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();
}
}