Job Chaining with Bus Chain

Job Chaining is used to run series of Laravel jobs one after another that depend on previous job. If one job of the chain failed rest will not executed.

  1. Used to execute series of job that has dependencies with previous jobs.
  2. Execution of jobs stops only if one of the job in the chain failed to execute.
  3. using $this->delete() on jobs does not stop executing other jobs.

Table of Content

  1. Job Chaining
  2. AppendToChain
  3. PrependToChain

How to run a Job Chain by defining a queue connection and the queue?

Bus::chain([
    new JobTaskOne,
    new JobTaskTwo,
    new JobTaskThree,
    new JobTaskFour
])->onConnection('redis')->onQueue('podcasts')->dispatch();

Example: Running a series of jobs without chaining

Let’s see what happen if we ran series of jobs without adding it to a job chain. First setup a fresh Laravel application. Then create 4 Laravel job classes using below artisan command.

php artisan make:job JobTaskOne
php artisan make:job JobTaskTwo
php artisan make:job JobTaskThree
php artisan make:job JobTaskFour

Add logs on all 4 job classes in the handle() method like below. Modify JobTaskTwo and JobTaskFour by adding logs like below example.

<?php

namespace App\Jobs;

use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;
use Illuminate\Support\Facades\Log;

class JobTaskOne implements ShouldQueue
{
    use Queueable;

    /**
     * Create a new job instance.
     */
    public function __construct()
    {
        //
    }

    /**
     * Execute the job.
     */
    public function handle(): void
    {
        Log::info('Completed - Job Task One');
    }
}

Modify JobTaskThree to throw exception during the execution to mark it as a failed job.

<?php

namespace App\Jobs;

use Exception;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;
use Illuminate\Support\Facades\Log;

class JobTaskThree implements ShouldQueue
{
    use Queueable;

    /**
     * Create a new job instance.
     */
    public function __construct()
    {
        //
    }

    /**
     * Execute the job.
     */
    public function handle(): void
    {
        Log::info('Completed - Job Task Three');
        throw new Exception();
    }
}

Now dispatch all four job classes. Open web.php file and add below code.

use Illuminate\Support\Facades\Bus;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Route;

use App\Jobs\JobTaskOne;
use App\Jobs\JobTaskTwo;
use App\Jobs\JobTaskThree;
use App\Jobs\JobTaskFour;

Route::get('/', function () {
    //Log request
    Log::info("Received a get request on root route");

    JobTaskOne::dispatch();
    JobTaskTwo::dispatch();
    JobTaskThree::dispatch();
    JobTaskFour::dispatch();

});

Now run Laravel web application

php artisan serve

Run queue worker

php artisan queue:listen

See Laravel logs.

All the Laravel Jobs executed even if a job on the chain is failed.

[2024-12-28 09:26:31] local.INFO: Received a get request on root route  
[2024-12-28 09:26:34] local.INFO: Completed - Job Task One  
[2024-12-28 09:26:34] local.INFO: Completed - Job Task Two  
[2024-12-28 09:26:35] local.INFO: Completed - Job Task Three  
[2024-12-28 09:26:35] local.ERROR:  {"exception":"[object] (Exception(code: 0): ..
[2024-12-28 09:26:36] local.INFO: Completed - Job Task Four  

Example: Running series of Jobs using Job Chaining

Task:

Run 4 Laravel jobs in a Job chain. Purposely fail third job of the chain. Make sure to catch any exceptions thrown by jobs in the job chain.

Open web.php file and add all four Laravel jobs to chain() method provided by Bus facade. Use catch() method to catch any exceptions thrown by jobs on the job chain.

<?php

use App\Jobs\JobTaskFour;
use App\Jobs\JobTaskOne;
use App\Jobs\JobTaskThree;
use App\Jobs\JobTaskTwo;
use Illuminate\Support\Facades\Bus;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Route;

Route::get('/', function () {
    //Log request
    Log::info("Received a get request on root route");

    Bus::chain([
        new JobTaskOne,
        new JobTaskTwo,
        new JobTaskThree,
        new JobTaskFour
    ])->catch(function (Throwable $e)
    {
        Log::info("Catch Exception: ".$e->getMessage());
    })
    ->dispatch();
});

Now run Laravel application and queue listener using artisan command and check the log file.

[2024-12-28 10:28:26] local.INFO: Received a get request on root route  
[2024-12-28 10:28:28] local.INFO: Completed - Job Task One  
[2024-12-28 10:28:29] local.INFO: Completed - Job Task Two  
[2024-12-28 10:28:29] local.INFO: Completed - Job Task Three  
[2024-12-28 10:28:29] local.INFO: Catch Exception: My Custom Exception message  
[2024-12-28 10:28:29] local.ERROR: My Custom Exception message {"exception":"[object]..

With chain() method you will after JobTaskThree failed JobTaskFour is not executed. 5th line shows the thrown exception.

Example: Add job to end of the chain (AppendToChain)

Create another job to append to job chain.

php artisan make:job ExtraJobTask

Add log to handle() method of the ExtraJobTask job class.

<?php

namespace App\Jobs;

use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;
use Illuminate\Support\Facades\Log;

class ExtraJobTask implements ShouldQueue
{
    use Queueable;

    /**
     * Create a new job instance.
     */
    public function __construct()
    {
        //
    }

    /**
     * Execute the job.
     */
    public function handle(): void
    {
        Log::info('Completed - extra job task');
    }
}

Now append this job after completing the JobTaskThree. For that open JobTaskThree.php file and modify like below.

  1. Remove previously defined exception.
  2. Add ExtraJobTask class to appendToChain() method.
<?php

namespace App\Jobs;

use Exception;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;
use Illuminate\Support\Facades\Log;

class JobTaskThree implements ShouldQueue
{
    use Queueable;

    /**
     * Create a new job instance.
     */
    public function __construct()
    {
        //
    }

    /**
     * Execute the job.
     */
    public function handle(): void
    {
        Log::info('Completed - Job Task Three');
        $this->appendToChain(new ExtraJobTask);        
    }
}

Now run this Laravel application and queue listener using the artisan command. Does not need to modify web.php file.

Logs:

[2024-12-28 10:09:31] local.INFO: Received a get request on root route  
[2024-12-28 10:09:33] local.INFO: Completed - Job Task One  
[2024-12-28 10:09:34] local.INFO: Completed - Job Task Two  
[2024-12-28 10:09:34] local.INFO: Completed - Job Task Three  
[2024-12-28 10:09:35] local.INFO: Completed - Job Task Four  
[2024-12-28 10:09:36] local.INFO: Completed - extra job task  

Even though ExtraJobTask defined inside the JobTaskThree it will execute after JobTaskFour(last job of the chain)

Example: Run a Job immediately after a selected job from a Job chain (prependToChain)

You can use prependToChain() method to run given job immediately after the defined job of the job chaining.

Open JobTaskThree and add below code.

<?php

namespace App\Jobs;

use Exception;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;
use Illuminate\Support\Facades\Log;

class JobTaskThree implements ShouldQueue
{
    use Queueable;

    /**
     * Create a new job instance.
     */
    public function __construct()
    {
        //
    }

    /**
     * Execute the job.
     */
    public function handle(): void
    {
        Log::info('Completed - Job Task Three');
        $this->prependToChain(new ExtraJobTask);        
    }
}

Run this application and queue listener. No need to modify web.php file.

Logs:

[2024-12-28 10:13:19] local.INFO: Received a get request on root route  
[2024-12-28 10:13:23] local.INFO: Completed - Job Task One  
[2024-12-28 10:13:23] local.INFO: Completed - Job Task Two  
[2024-12-28 10:13:24] local.INFO: Completed - Job Task Three  
[2024-12-28 10:13:25] local.INFO: Completed - extra job task  
[2024-12-28 10:13:25] local.INFO: Completed - Job Task Four  

ExtraJobTask was defined inside the JobTaskThree using prependToChain() method. Therefore ExtraJobTask executed right after completing the defined job class of the chain and continue executing rest of the jobs of the chain.