Failed Job in a Laravel Batch

What happen if one job of a Laravel Batch processing failed? Imagine 3rd job of the GeneratReport job class failed.

  1. $this->batch()->cancelled() will return true from 3rd job to rest of the jobs.
  2. You can use this $this->batch()->cancelled() to cancel rest of the jobs in the batch or continue executing. Just use return statement in a if else statement using above cancelled() method to stop executing rest of the jobs.

Full example

Imagine you have to generate reports using GenerateReport job class for 6 outlets. One report failed during the processs. Log the failed report in Laravel Logs and Process GenerateReport jobs in a batch processing.

Setup a fresh Laravel web application. Create a Laravel job class using below artisan command.

php artisan make:job GenerateReport

Open GenerateReport Job class and add followings.

  1. Add Batchable trait.
  2. Accept outlet name and status (used to purposely failed a selected job)
  3. Add 2 seconds sleep to simulate time consuming task.
  4. Throw an exception from the failed the job if the status variable is true.
  5. Use cancelled() method on the batch() method to check whether any job of the batch has failed.
  6. Log information.
<?php

namespace App\Jobs;

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

class GenerateReport implements ShouldQueue
{
    use Queueable, Batchable;

    /**
     * Create a new job instance.
     */
    public function __construct(public string $outletName, public bool $status) {}

    /**
     * Execute the job.
     */
    public function handle(): void
    {
        sleep(2);
        if($this->status){
            Log::info('Throw new exception - '.$this->outletName);
            throw new Exception();
        }

        if ($this->batch()->cancelled()) {
            // Determine if the batch has been cancelled...
            Log::info('Job has been cancelled '. $this->outletName);
            return;
        }

        Log::info('Completed Report for '. $this->outletName);
    }
}

Open web.php

  1. Create list of outlets names in a array.
  2. Create a empty array to hold initialized instances of GenerateReport jobs.
  3. Create array of GenerateReports jobs by iterating outlets array.
  4. Randomly set status to false on one instance of the GenerateReport job in the array.
  5. Dispatch jobs array in a batch.
<?php

use App\Jobs\GenerateReport;
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");

    //List of outlets
    $outlets = ['outlet_1','outlet_2', 'outlet_3','outlet_4','outlet_5','outlet_6' ];

    //Laravel jobs array
    $generateReportJobs = [];

    //Create instances of GenerateReport job class
    foreach($outlets as $key=>$outlet)
    {
        if($key == 2){
            Log::info("Marked as a failed job $outlet");
            array_push($generateReportJobs, new GenerateReport($outlet, true));
        }else{
            array_push($generateReportJobs, new GenerateReport($outlet, false));
        }
        
    }

    //Batch processing about created list of Laravel jobs
    Bus::batch($generateReportJobs)->dispatch();
});

Run this Laravel web application. Here I am using 3 queue workers to quickly finish the batch processing. For local development you can run below artisan command in 3 terminal windows.

php artisan queue:work

Logs:

  1. Thrown exception is captured by batch()->cancelled() method within the same job and remaining set of the jobs.
  2. You can either cancel or continue remaining set of jobs by handing the thrown exception separately.
[2025-01-01 11:56:04] local.INFO: Received a get request on root route  
[2025-01-01 11:56:04] local.INFO: Marked as a failed job outlet_3  
[2025-01-01 11:56:07] local.INFO: Completed Report for outlet_1  
[2025-01-01 11:56:08] local.INFO: Completed Report for outlet_2  
[2025-01-01 11:56:09] local.INFO: Throw new exception - outlet_3  
[2025-01-01 11:56:09] local.ERROR:  {"exception":"[object]"
[2025-01-01 11:56:10] local.INFO: Job has been cancelled outlet_4  
[2025-01-01 11:56:11] local.INFO: Job has been cancelled outlet_5  
[2025-01-01 11:56:12] local.INFO: Job has been cancelled outlet_6