{"id":16751,"date":"2024-03-22T06:00:00","date_gmt":"2024-03-22T06:00:00","guid":{"rendered":"https:\/\/www.directimpactsolutions.com\/?p=16751"},"modified":"2025-05-01T02:53:46","modified_gmt":"2025-05-01T02:53:46","slug":"job-batches-in-laravel","status":"publish","type":"post","link":"https:\/\/www.directimpactsolutions.com\/en\/job-batches-in-laravel\/","title":{"rendered":"Job Batches in Laravel"},"content":{"rendered":"<figure data-wp-context=\"{&quot;imageId&quot;:&quot;69ee2f5c9fde8&quot;}\" data-wp-interactive=\"core\/image\" class=\"wp-block-image size-full wp-lightbox-container\"><img loading=\"lazy\" decoding=\"async\" width=\"500\" height=\"300\" data-wp-class--hide=\"state.isContentHidden\" data-wp-class--show=\"state.isContentVisible\" data-wp-init=\"callbacks.setButtonStyles\" data-wp-on-async--click=\"actions.showLightbox\" data-wp-on-async--load=\"callbacks.setButtonStyles\" data-wp-on-async-window--resize=\"callbacks.setButtonStyles\" src=\"https:\/\/www.directimpactsolutions.com\/wp-content\/uploads\/2024\/02\/Job-Batches-in-Laravel.png\" alt=\"Job Batches in Laravel\" class=\"wp-image-16752\" srcset=\"https:\/\/www.directimpactsolutions.com\/wp-content\/uploads\/2024\/02\/Job-Batches-in-Laravel.png 500w, https:\/\/www.directimpactsolutions.com\/wp-content\/uploads\/2024\/02\/Job-Batches-in-Laravel-300x180.png 300w\" sizes=\"auto, (max-width: 500px) 100vw, 500px\" \/><button\n\t\t\tclass=\"lightbox-trigger\"\n\t\t\ttype=\"button\"\n\t\t\taria-haspopup=\"dialog\"\n\t\t\taria-label=\"Enlarge\"\n\t\t\tdata-wp-init=\"callbacks.initTriggerButton\"\n\t\t\tdata-wp-on-async--click=\"actions.showLightbox\"\n\t\t\tdata-wp-style--right=\"state.imageButtonRight\"\n\t\t\tdata-wp-style--top=\"state.imageButtonTop\"\n\t\t>\n\t\t\t<svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"12\" height=\"12\" fill=\"none\" viewBox=\"0 0 12 12\">\n\t\t\t\t<path fill=\"#fff\" d=\"M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z\" \/>\n\t\t\t<\/svg>\n\t\t<\/button><\/figure><p><\/p><p>When is it helpful to utilize job batches in Laravel?<\/p><p>We\u2019ve touched on queues <a href=\"https:\/\/www.directimpactsolutions.com\/en\/laravel-jobs-queue-worker\/\">in a previous article<\/a>. Queues are a useful tool in Laravel, allowing you to run complicated or time consuming tasks in the background. There will be times when you need to run dozens, or even hundreds of instances of a job \u2014 for instance, creating individual reports for a larger number of clients, or importing a folder of .csv files.<\/p><p>This could be accomplished with running a single job with a loop to run the required logic. The problem with this approach is scalability. It will probably work fine if you have small number of reports to create or files to import, but at certain point, execution time and memory usage for the job will exceed the limits you&#8217;ve defined in your PHP and server configuration.<\/p><p>While you could increase those limits, a more efficient approach would be to run each report as a separate job \u2014 which has a relatively small execution time and memory usage \u2014 then create a .zip file when all jobs complete. This can be accomplished using Job Batching.<\/p><div class=\"wp-block-rank-math-toc-block\" id=\"rank-math-toc\"><nav><ul><li class=\"\"><a href=\"#what-is-job-batching\">What is Job Batching?<\/a><\/li><li class=\"\"><a href=\"#creating-a-job-batch\">Creating a Job Batch<\/a><ul><li class=\"\"><a href=\"#creating-the-invoice-job\">Creating the Invoice Job<\/a><\/li><li class=\"\"><a href=\"#running-the-batch\">Running the Batch<\/a><\/li><\/ul><\/li><\/ul><\/nav><\/div><h2 class=\"wp-block-heading\" id=\"what-is-job-batching\" style=\"font-style:normal;font-weight:400\">What is Job Batching?<\/h2><p>Job Batching is a feature in Laravel. As the name implies, it allows you to execute a batch of jobs. Data about the batch is stored in a table called <em>job_batches<\/em>; this contains useful information about the batch, such as the total number of jobs, the number of failed jobs, the number of jobs still in progress, etc. This table is created using the following commands:<\/p><pre class=\"wp-block-code\"><code>php artisan queue:batches-table\nphp artisan migrate<\/code><\/pre><p>In addition, you can define a number of callback functions that run when certain events occur. These include:<\/p><ul class=\"wp-block-list\"><li>Progress \u2013 runs when a single job is successfully completed<\/li>\n\n<li>Then \u2013 runs when all jobs are successfully completed<\/li>\n\n<li>Catch \u2013 runs the first time a job failure is detected<\/li>\n\n<li>Finally \u2013 runs when all jobs have been executed (successfully or not)<\/li><\/ul><h2 class=\"wp-block-heading\" id=\"creating-a-job-batch\" style=\"font-style:normal;font-weight:400\">Creating a Job Batch<\/h2><p>In order to demonstrate what you can accomplish with batches we\u2019ll use the example of creating a monthly Invoice as a PDF for each of the clients in a hypothetical billing system. For our purposes, the invoices will be saved to a .zip folder to be reviewed before they are sent. The workflow will look something like this:<\/p><ol start=\"1\" class=\"wp-block-list\"><li>Find all clients that need an invoice.<\/li>\n\n<li>Loop through each of the clients, generate the invoice, and save the PDF on the server.<\/li>\n\n<li>Zip the invoices into a single file.<\/li>\n\n<li>Email the user who requested the invoices with a link to the .zip file.<\/li>\n\n<li>If any of the PDFs fail, cancel and inform the user that there was an issue.<\/li><\/ol><p>We\u2019ll keep things simple and assume there is an invoice model that has methods to find the list of clients and generate the invoice. We\u2019ll also keep the error handling pretty minimal and just send an email if any of the invoices fail; we\u2019ll touch on some improvements we can make later.<\/p><h3 class=\"wp-block-heading has-ast-global-color-2-color has-text-color has-link-color wp-elements-913158fc62175c904450213c54a4c4e7\" id=\"creating-the-invoice-job\">Creating the Invoice Job<\/h3><p>Starting off, we\u2019ll create the job for generating a single PDF invoice. We\u2019ll pass the invoice ID, and, in the handler, find the appropriate invoice and call the method to generate and save the invoice PDF. In order to make it a batchable job, we will need to add the <em>Illuminate\\Bus\\Batchable<\/em> trait to the job class. This trait provides access to a batch method which will allow us to retrieve the batch the job belongs to.<\/p><p>We\u2019ll start by creating the a job called <em>CreateInvoice<\/em> using an artisan command.<\/p><pre class=\"wp-block-code\"><code>php artisan make:job CreateInvoice<\/code><\/pre><p>After adding the <em>Illuminate\\Bus\\Batchable<\/em> trait and the code to generate the invoice, the job class looks like this:<\/p><pre class=\"wp-block-code\"><code>&lt;?php\n\nnamespace App\\Jobs;\n\nuse Illuminate\\Bus\\Batchable;\nuse Illuminate\\Bus\\Queueable;\nuse Illuminate\\Contracts\\Queue\\ShouldBeUnique;\nuse Illuminate\\Contracts\\Queue\\ShouldQueue;\nuse Illuminate\\Foundation\\Bus\\Dispatchable;\nuse Illuminate\\Queue\\InteractsWithQueue;\nuse Illuminate\\Queue\\SerializesModels;\nuse App\\Models\\Invoice;\n\nclass CreateInvoice implements ShouldQueue\n{\n    use Batchable, Dispatchable, InteractsWithQueue, Queueable, SerializesModels;\n\n    private $invoice_id;\n\n    \/**\n     * Create a new job instance.\n     *\/\n    public function __construct($invoice_id)\n    {\n        $this-&gt;invoice_id = $invoice_id;\n    }\n\n    \/**\n     * Execute the job.\n     *\/\n    public function handle(): void\n    {\n        if ($this-&gt;batch()-&gt;cancelled()) {\n            \/\/ Determine if the batch has been cancelled...\n\n            return;\n        }\n        \n        $invoice = Invoice::find($this-&gt;invoice_id);\n        $invoice-&gt;createPDF();\n    }\n}\n<\/code><\/pre><p>The handler now consists of 3 main parts:<\/p><ol start=\"1\" class=\"wp-block-list\"><li>An if statement that detects if the batch has been cancelled \u2014 this most often occurs if a job fails. This prevents any further invoices from being generated in case any of them fail.<\/li>\n\n<li>Creating an invoice object using the ID passed into the job<\/li>\n\n<li>Calling the createPDF method in the invoice model to create a PDF version of the invoice and save it to disk.<\/li><\/ol><h3 class=\"wp-block-heading has-ast-global-color-2-color has-text-color has-link-color wp-elements-376182750f55899258baa50a84df05d5\" id=\"running-the-batch\">Running the Batch<\/h3><p>In order to run our new batch job, we will need to call the <em>batch<\/em> method of the <em>Illuminate\\Bus\\Batch<\/em><strong> <\/strong>fa\u00e7ade. We\u2019ll start with a basic call, just creating a handful of invoices as an example:<\/p><pre class=\"wp-block-code\"><code>use App\\Jobs\\CreateInvoice;\nuse Illuminate\\Bus\\Batch;\nuse Illuminate\\Support\\Facades\\Bus;\nuse Throwable;\n\n$batch = Bus::batch(&#91;\n    new CreateInvoice(1),\n    new CreateInvoice(2),\n    new CreateInvoice(3),\n    new CreateInvoice(4),\n])-&gt;then(function (Batch $batch) {\n    \/\/ All jobs completed successfully...\n})-&gt;catch(function (Batch $batch, Throwable $e) {\n    \/\/ First batch job failure detected...\n})-&gt;dispatch();<\/code><\/pre><p>We\u2019ve called the batch function, passing in four <em>CreateInvoice<\/em> jobs. We\u2019ve also defined the <em>then<\/em> callback, where we\u2019ll zip up the files and send a link to the file, and the <em>catch<\/em> callback, where we&#8217;ll send an error message to the user in case one of the jobs fails.<\/p><p>Of course, we\u2019re going to want to create more than four invoices. We\u2019ll abstract things again, and assume we have a static method in the invoice model, called <em>getInvoicesByMonth<\/em>, that returns a list of invoices for a specific month, as well as a method, called <em>createZipFile<\/em>, that will zip the invoices up and return a link to the resulting file. Now we\u2019ll create a new batchable job called <em>LoadInvoiceBatch<\/em>:<\/p><pre class=\"wp-block-code\"><code>&lt;?php\n\nnamespace App\\Jobs;\n\nuse App\\Models\\Invoice;\nuse Illuminate\\Bus\\Batchable;\nuse Illuminate\\Bus\\Queueable;\nuse Illuminate\\Contracts\\Queue\\ShouldBeUnique;\nuse Illuminate\\Contracts\\Queue\\ShouldQueue;\nuse Illuminate\\Foundation\\Bus\\Dispatchable;\nuse Illuminate\\Queue\\InteractsWithQueue;\nuse Illuminate\\Queue\\SerializesModels;\n\nclass LoadInvoiceBatch implements ShouldQueue\n{\n    use Batchable, Dispatchable, InteractsWithQueue, Queueable, SerializesModels;\n\n    private $month;\n\n    \/**\n     * Create a new job instance.\n     *\/\n    public function __construct($month)\n    {\n        $this-&gt;$month = $month;\n    }\n\n    \/**\n     * Execute the job.\n     *\/\n    public function handle(): void\n    {\n        if ($this-&gt;batch()-&gt;cancelled()) {\n            return;\n        }\n\n        $invoice_list = Invoice::getInvoicesByMonth($this-&gt;month);\n        foreach ($invoice_list as $invoice) {\n            $this-&gt;batch()-&gt;add(new CreateInvoice($invoice-&gt;id));\n        }\n    }\n}<\/code><\/pre><p>With this in place, we can run our new job and define our callbacks:<\/p><pre class=\"wp-block-code\"><code>use App\\Jobs\\LoadInvoiceBatch;\nuse App\\Models\\Invoice;\nuse App\\Notifications\\InvoiceError;\nuse App\\Notifications\\InvoicesCompleted;\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Support\\Facades\\Auth;\nuse Illuminate\\Support\\Facades\\Bus;\n\n$batch = Bus::batch(&#91;\n    new LoadInvoiceBatch($month)\n])-&gt;then(function() {\n    $link = Invoice::createZipFile();\n    Auth::user()-&gt;notify(new InvoicesCompleted($link));\n})-&gt;catch(function() {\n    Auth::user()-&gt;notify(new InvoiceError());\n})-&gt;dispatch();<\/code><\/pre><p>As part of creating the batch, we\u2019ve stored it in a variable called <em>$batch<\/em>; this has a number of useful properties and methods that give us information about the batch, including the batch ID, the total number of jobs in the batch, the number of pending jobs, the number of failed jobs, the percentage of jobs complete, and more. There is also a method to cancel the job manually (if you need to). This example was fairly simple; there is more you can do with batches, such as attempting to retry any failed jobs in the batch. You can find more information in the <a href=\"https:\/\/laravel.com\/docs\/10.x\/queues#job-batching\" target=\"_blank\" rel=\"noreferrer noopener\">Laravel Documentation<\/a>.<\/p>","protected":false},"excerpt":{"rendered":"<p>When is it helpful to utilize job batches in Laravel? We\u2019ve touched on queues in a previous article. Queues are a useful tool in Laravel, allowing you to run complicated or time consuming tasks in the background. There will be times when you need to run dozens, or even hundreds of instances of a job &hellip;<\/p>\n<p class=\"read-more\"> <a class=\"\" href=\"https:\/\/www.directimpactsolutions.com\/en\/job-batches-in-laravel\/\"> <span class=\"screen-reader-text\">Job Batches in Laravel<\/span> Read More &raquo;<\/a><\/p>\n","protected":false},"author":9,"featured_media":16752,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"inline_featured_image":false,"_uag_custom_page_level_css":"","site-sidebar-layout":"default","site-content-layout":"","ast-site-content-layout":"default","site-content-style":"default","site-sidebar-style":"default","ast-global-header-display":"","ast-banner-title-visibility":"","ast-main-header-display":"","ast-hfb-above-header-display":"","ast-hfb-below-header-display":"","ast-hfb-mobile-header-display":"","site-post-title":"","ast-breadcrumbs-content":"","ast-featured-img":"","footer-sml-layout":"","theme-transparent-header-meta":"","adv-header-id-meta":"","stick-header-meta":"","header-above-stick-meta":"","header-main-stick-meta":"","header-below-stick-meta":"","astra-migrate-meta-layouts":"set","footnotes":""},"categories":[32],"tags":[307,87],"class_list":["post-16751","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-web-applications","tag-job-batches","tag-laravel"],"uagb_featured_image_src":{"full":["https:\/\/www.directimpactsolutions.com\/wp-content\/uploads\/2024\/02\/Job-Batches-in-Laravel.png",500,300,false],"thumbnail":["https:\/\/www.directimpactsolutions.com\/wp-content\/uploads\/2024\/02\/Job-Batches-in-Laravel-150x150.png",150,150,true],"medium":["https:\/\/www.directimpactsolutions.com\/wp-content\/uploads\/2024\/02\/Job-Batches-in-Laravel-300x180.png",300,180,true],"medium_large":["https:\/\/www.directimpactsolutions.com\/wp-content\/uploads\/2024\/02\/Job-Batches-in-Laravel.png",500,300,false],"large":["https:\/\/www.directimpactsolutions.com\/wp-content\/uploads\/2024\/02\/Job-Batches-in-Laravel.png",500,300,false],"1536x1536":["https:\/\/www.directimpactsolutions.com\/wp-content\/uploads\/2024\/02\/Job-Batches-in-Laravel.png",500,300,false],"2048x2048":["https:\/\/www.directimpactsolutions.com\/wp-content\/uploads\/2024\/02\/Job-Batches-in-Laravel.png",500,300,false],"woocommerce_thumbnail":["https:\/\/www.directimpactsolutions.com\/wp-content\/uploads\/2024\/02\/Job-Batches-in-Laravel-300x300.png",300,300,true],"woocommerce_single":["https:\/\/www.directimpactsolutions.com\/wp-content\/uploads\/2024\/02\/Job-Batches-in-Laravel.png",500,300,false],"woocommerce_gallery_thumbnail":["https:\/\/www.directimpactsolutions.com\/wp-content\/uploads\/2024\/02\/Job-Batches-in-Laravel-100x100.png",100,100,true]},"uagb_author_info":{"display_name":"Alan Bruce","author_link":"https:\/\/www.directimpactsolutions.com\/en\/author\/alan-bruce\/"},"uagb_comment_info":0,"uagb_excerpt":"When is it helpful to utilize job batches in Laravel? We\u2019ve touched on queues in a previous article. Queues are a useful tool in Laravel, allowing you to run complicated or time consuming tasks in the background. There will be times when you need to run dozens, or even hundreds of instances of a job&hellip;","_links":{"self":[{"href":"https:\/\/www.directimpactsolutions.com\/en\/wp-json\/wp\/v2\/posts\/16751","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.directimpactsolutions.com\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.directimpactsolutions.com\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.directimpactsolutions.com\/en\/wp-json\/wp\/v2\/users\/9"}],"replies":[{"embeddable":true,"href":"https:\/\/www.directimpactsolutions.com\/en\/wp-json\/wp\/v2\/comments?post=16751"}],"version-history":[{"count":3,"href":"https:\/\/www.directimpactsolutions.com\/en\/wp-json\/wp\/v2\/posts\/16751\/revisions"}],"predecessor-version":[{"id":19970,"href":"https:\/\/www.directimpactsolutions.com\/en\/wp-json\/wp\/v2\/posts\/16751\/revisions\/19970"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.directimpactsolutions.com\/en\/wp-json\/wp\/v2\/media\/16752"}],"wp:attachment":[{"href":"https:\/\/www.directimpactsolutions.com\/en\/wp-json\/wp\/v2\/media?parent=16751"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.directimpactsolutions.com\/en\/wp-json\/wp\/v2\/categories?post=16751"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.directimpactsolutions.com\/en\/wp-json\/wp\/v2\/tags?post=16751"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}