Execute Sub-Batches for Every Job in a Main Laravel Batch

How to run a sub batch for every job instance in a batch of Laravel? Check the image for quick clarification.

Mail Campaign Batch (Main)
Mail Campaign Batch (Main)
Mail Campaign job instance
Mail Campaign job instance
Mail Campaign job instance
Mail Campaign job instance
Mail Campaign job instance
Mail Campaign job instance
Mail Campaign job instance
Mail Campaign job instance
Send Mail Batch (sub)
Send Mail Batch (sub)
send mail instance
send mail instance
send mail instance
send mail instance
send mail instance
send mail instance
send mail instance
send mail instance
send mail instance
send mail instance
send mail instance
send mail instance
Send Mail Batch (Sub)
Send Mail Batch (Sub)
send mail instance
send mail instance
send mail instance
send mail instance
send mail instance
send mail instance
send mail instance
send mail instance
send mail instance
send mail instance
send mail instance
send mail instance
Send Mail Batch (Sub)
Send Mail Batch (Sub)
send mail instance
send mail instance
send mail instance
send mail instance
send mail instance
send mail instance
send mail instance
send mail instance
send mail instance
send mail instance
send mail instance
send mail instance
Send Mail Batch (Sub)
Send Mail Batch (Sub)
send mail instance
send mail instance
send mail instance
send mail instance
send mail instance
send mail instance
send mail instance
send mail instance
send mail instance
send mail instance
send mail instance
send mail instance
Text is not SVG – cannot display
  1. Make sure all used job classes has Batchable trait.
  2. Use add() method of the batch() method to create your sub batch for that job instance of the main batch.
  3. You can add single job to every job instance of the batch using add() method.
  4. You can create a another batch or sub batch for every job instance of the batch.
$this->batch()->add(
     array of job class instances
     single job class instance |
     enumerable or collection of job class instances
)
....
use Illuminate\Support\Collection;
....
class MainBatchJob implements ShouldQueue
{
    use Queueable, Batchable;

     .....
    /**
     * Execute the job.
     */
    public function handle(): void
    {
       //Creating a sub job batch using Collection
        $this->batch()->add(Collection::times(5, function($key){
            return new SubJob;
        }));
    }
}

Imagine you have to a create mail campaigns for 5 products. Each product has mail subscribers. When you create a mail campaign for a product it should create a batch to send mail to all its subscribers.

First create two required jobs as PrepareMailCampaign and SendMailToSubscriber using below artisan command.

php artisan make:job PrepareMailCampaign
php artisan make:job SendMailToSubscriber

Open SendMailToSubscriber job class and add below code

  1. Accept product name and key (it may be user id, mailer id, etc.) via the constructor.
  2. Log product name and key
<?php

namespace App\Jobs;

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

class SendMailToSubscriber implements ShouldQueue
{
    use Queueable, Batchable;

    /**
     * Create a new job instance.
     */
    public function __construct(
        public string $productName, 
        public string $key
    )
    {
        //
    }

    /**
     * Execute the job.
     */
    public function handle(): void
    {
        Log::info("Complete Sending mail for 
                          {$this->key} user - {$this->productName}" );
    }
}

Open PrepareMailCampaign and add below code.

  1. Accept product name via the constructor.
  2. Add your sub batch which is mail sending batch in the handle() method.
<?php

namespace App\Jobs;

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

class PrepareMailCampaign implements ShouldQueue
{
    use Queueable, Batchable;

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

    /**
     * Execute the job.
     */
    public function handle(): void
    {
        Log::info("Complete Campaign - ". $this->productName);
        $this->batch()->add(Collection::times(5, function($key){
            return new SendMailToSubscriber($this->productName, $key);
        }));
    }
}

Open web.php file and add below code.

  1. Create a array to hold product names
  2. Create a empty array hold array of jobs for batch.
  3. Iterate products array and create batch.
  4. Execute the batch.
<?php

use App\Jobs\PrepareMailCampaign;
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 products that require main campaigns.
    $products = ['prod_1', 'prod_2', 'prod_3', 'prod_4', 'prod_5'];

    //Create empty batch array to hold prepareMainCampaign jobs
    $batch = [];

    //Create the mailCampaign Batch
    foreach($products as $product)
    {
        array_push($batch, new PrepareMailCampaign($product));
    }

    //Dispatch the batch
    Bus::batch($batch)->dispatch();

});

Run two instances of queue workers using below artisan command.

php artisan queue:work

Logs:

  1. Sub batches are execute after completing the main batch.
  2. Sub batches are execute in order instead of executing randomly.
[2025-01-06 05:26:30] local.INFO: Received a get request on root route

//Completed the main batch
[2025-01-06 05:26:31] local.INFO: Complete Campaign - prod_1  
[2025-01-06 05:26:33] local.INFO: Complete Campaign - prod_2  
[2025-01-06 05:26:35] local.INFO: Complete Campaign - prod_3  
[2025-01-06 05:26:37] local.INFO: Complete Campaign - prod_4  
[2025-01-06 05:26:39] local.INFO: Complete Campaign - prod_5  

//Completed the sub batch for product 1
[2025-01-06 05:26:41] local.INFO: Complete Sending mail for 1 user - prod_1  
[2025-01-06 05:26:41] local.INFO: Complete Sending mail for 2 user - prod_1  
[2025-01-06 05:26:41] local.INFO: Complete Sending mail for 3 user - prod_1  
[2025-01-06 05:26:41] local.INFO: Complete Sending mail for 4 user - prod_1  
[2025-01-06 05:26:41] local.INFO: Complete Sending mail for 5 user - prod_1 

//Completed the sub batch for product 2
[2025-01-06 05:26:41] local.INFO: Complete Sending mail for 1 user - prod_2  
[2025-01-06 05:26:41] local.INFO: Complete Sending mail for 2 user - prod_2  
[2025-01-06 05:26:41] local.INFO: Complete Sending mail for 3 user - prod_2  
[2025-01-06 05:26:41] local.INFO: Complete Sending mail for 4 user - prod_2  
[2025-01-06 05:26:41] local.INFO: Complete Sending mail for 5 user - prod_2

....