Iterate over serviceproviders in laravel cron - laravel

I have inherited this laravel app and still get accustomed to the framework.
At the moment I try to figure out the "best-practise-way" of iterating through "Service Providers" in a cron job. I have set up the schedule, but I know that just creating an object (or object factory) is probably a big no-no.
How can I get access to an array of all the components in this cron job. something with singeltons probably.
The code:
protected function schedule(Schedule $schedule)
{
$schedule->call(function () {
$some_array = get_all_the_company_components_or_eloquents();
foreach( $some_array as $company )
{
$company->yay("yay");
}
})->daily();
}

Related

How to store records on the end of the day in laravel

For example, If user forgot to approve till the end of the day a message will store to the database at the end of the day. I used the carbon format as below.
if (Carbon::now()->endOfDay()->eq(Carbon::parse($phaseValue->end_date)->endOfDay())) {
// add records here
}
You will have to set up a task scheduler that will check if a specific condition has been met at the end of the day. if not then it will do the insertion in DB or anything according to your business logic. Laravel provides task scheduling, have a look:
https://laravel.com/docs/8.x/scheduling
You could create a cron job that would run every day at the given time. To do that, create a new command:
php artisan make:command YourCommand
Then, your command would look something like this:
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
class YourCommand extends Command
{
//Name that will be used to call command
protected $signature = 'your:command';
protected $description = 'Command description';
public function __construct()
{
parent::__construct();
}
public function handle()
{
//Here you will handle all the logic. For ex:
$record = App/Example::first();
//Implement condition that will determine if the record will be updated
$record->update();
}
}
After that, inside App/Console/Kernel.php create the schedule for that command:
protected function schedule(Schedule $schedule)
{
//Name that you gave it in the protected $signature = 'your:command';
$schedule->command('your:command')->dailyAt('23:30');
}
This way the command will be run every day at 23:30 and you can update your records any way you want. You can read more about task scheduling & commands on the official docs.

Laravel Nested Jobs

I created a job that has a foreach loop that dispatches another job. Is there a way to fire an even when all the nested jobs are completed?
When triggered here is what happends
Step 1. first I trigger the batch job
GenerateBatchReports::dispatch($orderable);
Step 2. We then run a loop and queue other jobs
/**
* Execute the job.
*
* #return void
*/
public function handle()
{
$dir = storage_path('reports/tmp/'.str_slug($this->event->company) . '-event');
if(file_exists($dir)) {
File::deleteDirectory($dir);
}
foreach($this->event->participants as $participant) {
$model = $participant->exercise;
GenerateSingleReport::dispatch($model);
}
}
I just need to know when all the nested jobs are done so I can zip the reports up and email them to a user. When the batch job is done queueing all the nested jobs, it is removed from the list. Is there a way to keep the job around until the nested jobs are done, then fire an event?
Any help would be appreciated.
Update: Laravel 8 (released planned on 8th September 2020) will provide jobs batching. This feature is already documented is probably perfect for nested jobs scenario and looks like this:
$batch = Bus::batch([
new ProcessPodcast(Podcast::find(1)),
new ProcessPodcast(Podcast::find(2)),
new ProcessPodcast(Podcast::find(3)),
new ProcessPodcast(Podcast::find(4)),
new ProcessPodcast(Podcast::find(5)),
])->then(function (Batch $batch) {
// All jobs completed successfully...
})->catch(function (Batch $batch, Throwable $e) {
// First batch job failure detected...
})->finally(function (Batch $batch) {
// The batch has finished executing...
})->dispatch();
We will also be able to add additional batched jobs on the fly:
$this->batch()->add(Collection::times(1000, function () {
return new ImportContacts;
}));
Original answer 👇
I came up with a different solution, because I have a queue using several processes. So, for me:
No dispatchNow because I want to keep jobs running in parallel.
Having several processes, I need to make sure the last nested job will not run after the final one. So a simple chaining doesn’t guarantee that.
So my not elegant solution filling the requirements is to dispatch all the nested jobs and, in the last one, dispatching the final job with a couple of seconds of delay, to make sure all other nested jobs that may still be running in parallel will be terminated.
/**
* Execute the job.
*
* #return void
*/
public function handle()
{
$last_participant_id = $this->event->participants->last()->id;
foreach($this->event->participants as $participant) {
$is_last = $participant->id === $last_participant_id;
GenerateSingleReport::dispatch($model, $is_last);
}
}
and in GenerateSingleReport.php
class GenerateSingleReport implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $model;
protected $runFinalJob;
public function __construct($model, $run_final_job = false)
{
$this->model = $model;
$this->runFinalJob = $run_final_job;
}
public function handle()
{
// job normal stuff…
if ($this->runFinalJob) {
FinalJob::dispatch()->delay(30);
}
}
}
Alternatively
I’m throwing another idea, so the code is not flawless. Maybe a wrapper Job could be created and dedicated to running the last nested job chained with the final job.
/**
* Execute the job.
*
* #return void
*/
public function handle()
{
$last_participant_id = $this->event->participants->last()->id;
foreach($this->event->participants as $participant) {
$is_last = $participant->id === $last_participant_id;
if ($is_last) {
ChainWithDelay::dispatch(
new GenerateSingleReport($model), // last nested job
new FinalJob(), // final job
30 // delay
);
} else {
GenerateSingleReport::dispatch($model, $is_last);
}
}
}
And in ChainWithDelay.php
class ChainWithDelay implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $job;
protected $finalJob;
protected $delay;
public function __construct($job, $final_job, $delay = 0)
{
$this->job = $job;
$this->finalJob = $final_job;
$this->delay = $delay;
}
public function handle()
{
$this->job
->withChain($this->finalJob->delay($this->delay))
->dispatchNow();
}
}
For laravel >= 5.7
You can use the dispatchNow method. That will keep the parent job alive while the child jobs are processing:
https://laravel.com/docs/5.8/queues#synchronous-dispatching
Parent job:
public function handle()
{
// ...
foreach($this->event->participants as $participant) {
$model = $participant->exercise;
GenerateSingleReport::dispatchNow($model);
}
// then do something else...
}
For laravel 5.2 - 5.6
You could use the sync connection:
https://laravel.com/docs/5.5/queues#customizing-the-queue-and-connection
Make sure the connection is defined in your config/queue.php:
https://github.com/laravel/laravel/blob/5.5/config/queue.php#L31
Parent job (NOTE: This syntax is for 5.5. The docs are a little different for 5.2):
public function handle()
{
// ...
foreach($this->event->participants as $participant) {
$model = $participant->exercise;
GenerateSingleReport::dispatch($model)->onConnection('sync');
}
// then do something else...
}
You could use Laravel's job chaining. It allows you to run a bunch of jobs in sequence and if one fails, the rest in the chain will not be run.
The basic syntax looks like this:
FirstJob::withChain([
new SecondJob($param),
new ThirdJob($param)
])->dispatch($param_for_first_job);
In your case your could add all of your GenerateSingleReport jobs to an array except the first one and add then add the final job that you want to run to the end of the array. Then you can pass that array to the withChain method on the first job.
$jobs = [];
$first_job = null;
$first_parameter = null;
foreach($this->event->participants as $participant) {
$model = $participant->exercise;
if (empty($first_job)) {
$first_job = GenerateSingleReport;
$first_parameter = $model;
} else {
$jobs[] = new GenerateSingleReport($model);
}
}
$jobs[] = new FinalJob();
$first_job->withChain($jobs)->dispatch($first_parameter);

Cache Eloquent query for response

In one of my applications I have a property that is needed throughout the app.
Multiple different parts of the application need access such as requests, local and global scopes but also commands.
I would like to "cache" this property for the duration of a request.
My current solution in my Game class looks like this:
/**
* Get current game set in the .env file.
* #return Game
*/
public static function current()
{
return Cache::remember('current_game', 1, function () {
static $game = null;
$id = config('app.current_game_id');
if ($game === null || $game->id !== $id) {
$game = Game::find($id);
}
return $game;
});
}
I can successfully call this using Game::current() but this solutions feels "hacky" and it will stay cached over the course of multiple requests.
I tried placing a property on the current request object but this won't be usable for the commands and seems inaccessible in the blade views and the objects (without passing the $request variable.
Another example of its usage is described below:
class Job extends Model
{
/**
* The "booting" method of the model.
*
* #return void
*/
protected static function boot()
{
parent::boot();
static::addGlobalScope('game_scope', function (Builder $builder) {
$builder->whereHas('post', function ($query) {
$query->where('game_id', Game::current()->id);
});
});
}
}
I do not believe I could easily access a request property in this boot method.
Another idea of mine would be to store the variable on a Game Facade but I failed to find any documentation on this practice.
Could you help me find a method of "caching" the Game::current() property accessible in most if not all of these cases without using a "hacky" method.
Use the global session helper like this:
// Retrieve a piece of data from the session...
$value = session('key');
// Store a piece of data in the session...
session(['key' => 'value']);
For configuration info and more options: https://laravel.com/docs/5.7/session

Laravel nova resource extending/overriding the create method

I am developing a web admin panel using Laravel Nova.
I am having an issue since Nova is quite a new technology.
What I would like to do now is I would like to add a hidden field or extend or override the create method.
This is my scenario. Let's say I have a vacancy nova resource with the following field.
public function fields(Request $request)
{
return [
ID::make()->sortable(),
Text::make('Title')->sortable(),
Text::make('Salary')->sortable()
// I will have another field, called created_by
];
}
Very simple. What I like to do is I want to add a new field called created_by into the database. Then that field will be auto filled with the current logged user id ($request->user()->id).
How can I override or extend the create function of Nova? How can I achieve it?
I can use resource event, but how can I retrieve the logged in user in
the event?
What you're looking for is Resource Events.
From the docs:
All Nova operations use the typical save, delete, forceDelete, restore Eloquent methods you are familiar with. Therefore, it is easy to listen for model events triggered by Nova and react to them.
The easiest approach is to simply attach a model observer to a model:
If you don't feel like creating a new observable you could also create a boot method in your eloquent model as so:
public static function boot()
{
parent::boot();
static::creating(function ($vacancy) {
$vacancy->created_by = auth()->user()->id;
});
}
But please do note that these are a bit harder to track than observables, and you or a next developer in the future might be scratching their head, wondering how's the "created_at" property set.
In my opinion you should go for Observers. Observers will make you code more readable and trackable.
Here is how you can achieve the same with Laravel Observers.
AppServiceProvider.php
public function boot()
{
Nova::serving(function () {
Post::observe(PostObserver::class);
});
}
PostObserver.php
public function creating(Post $post)
{
$post->created_by = Auth::user()->id;
}
OR
You can simply hack a Nova field using withMeta.
Text::make('created_by')->withMeta([
'type' => 'hidden',
'value' => Auth::user()->id
])
You could also do that directly within your Nova resource. Every Nova resource has newModel() method which is called when resource loads fresh instance of your model from db. You can override it and put there your logic for setting any default values (you should always check if values already exist, and only set if they are null, which will only be the case when the model is being created for the first time, which is what you actually need):
public static function newModel()
{
$model = static::$model;
$instance = new $model;
if ($instance->created_by == null) {
$instance->created_by = auth()->user()->id;
}
return $instance;
}
a) Create an Observer class with following command:
php artisan make:observer -m "Post" PostObserver
b) Add following code in the PostObserver:
$post->created_by = Auth::user()->id;
c) Register PostObserver in AppServiceProvider.php
For detailed explanation: https://medium.com/vineeth-vijayan/how-to-add-a-new-field-in-laravel-nova-resource-87f79427d38c
Since Nova v3.0, there is a native Hidden field.
Usage:
Hidden::make('Created By', 'created_by')
->default(
function ($request) {
return $request->user()->id;
}),
Docs: https://nova.laravel.com/docs/3.0/resources/fields.html#hidden-field

laravel task scheduler deleting a post

I setup a simple task scheduler in laravel everything works only problem that i am having is that the post is not deleting at the time i set. I want the post to delete after 5 minutes since the post was created at, not sure why my posts are deleting after a minute.I believe i want my task scheduler to check after every minute because each post has a different delete time. here my scheduler:
protected function schedule(Schedule $schedule)
{
$schedule->call(function (){
$post= Post::where('created_at', '<', Carbon::now()->addMinutes(5))->delete();
})->everyMinute();
}
From your question it would look as though you should be subtracting the minutes, not adding them
protected function schedule(Schedule $schedule)
{
$schedule->call(function () {
Post::where('created_at', '<', \Carbon\Carbon::now()->subMinutes(5))->delete();
})->everyMinute();
}
If you are just using Carbon::now() make sure you import it with a use statement. If you use it like I have in this example you don't need to import it.
Also since you're not using the $post variable after delete, you do not need to assign it.

Resources