DEV Community

Mahmoud ELDegwey
Mahmoud ELDegwey

Posted on

Print Multiple Order Labels as a Single PDF using Laravel

In this tutorial, I'll walk you through implementing a feature in Laravel to print multiple orders' labels as a single PDF file, especially for scenarios where there are more than 1,000 orders. Handling large numbers of files can lead to timeouts and errors, so we'll solve this issue by using Laravel queues for processing.

Problem Statement

When asked to implement a feature to print multiple order labels and merge them into one PDF for more than 2,000 orders using the traditional way, the system can encounter timeouts or exceptions due to take long execution. To resolve this, we can use Laravel queues to process the files in batches.

Step 1: Create a Laravel Project

composer create-project --prefer-dist laravel/laravel laravelPrintMultiLabel  
cd laravelPrintMultiLabel
Enter fullscreen mode Exit fullscreen mode

Update the .env file with your database configuration and change queue connection


DB_CONNECTION=mysql  
DB_HOST=127.0.0.1  
DB_PORT=3306  
DB_DATABASE=laravelPrintMultiLabel  
DB_USERNAME=root  
DB_PASSWORD=root  

QUEUE_CONNECTION=database  
Enter fullscreen mode Exit fullscreen mode

Step 2: Create a Model and Migration

php artisan make:model Order -m
php artisan make:queue-batches-table
php artisan migrate
Enter fullscreen mode Exit fullscreen mode

Hereโ€™s the code for the Order model:

namespace App\Models
use Illuminate\Database\Eloquent\Model;

class Order extends Model
{
    protected $fillable = [
        'order_number',
        'total_price',
        'product_count',
        'user_id',
        'status',
    ];

    protected $casts = [
        'created_at' => 'datetime:Y-m-d H:i:s',
        'updated_at' => 'datetime:Y-m-d H:i:s',
    ];
}
Enter fullscreen mode Exit fullscreen mode

Migration file:

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration {
    public function up(): void {
        Schema::create('orders', function (Blueprint $table) {
            $table->id();
            $table->foreignId('user_id')->constrained('users');
            $table->string('order_number', 50);
            $table->decimal('total_price', 8, 2);
            $table->integer('product_count');
            $table->enum('status', ['delivered', 'pending']);
            $table->timestamps();
            $table->softDeletes();
        });
    }

    public function down(): void {
        Schema::dropIfExists('orders');
    }
};
Enter fullscreen mode Exit fullscreen mode

Run the migration:

php artisan migrate
Enter fullscreen mode Exit fullscreen mode

Step 3: Add Dummy Data

Use Laravel seeders to add to the orders table with dummy data for testing:

php artisan make:seeder OrderSeeder
Enter fullscreen mode Exit fullscreen mode

Step 4: Install Required Packages

Install the packages for generating and merging PDF files:

composer require barryvdh/laravel-snappy
composer require webklex/laravel-pdfmerger

Enter fullscreen mode Exit fullscreen mode

Step 5: Create the Job Classes

Generate two job classes:

php artisan make:job MultiOrdersLabelPdfExport
php artisan make:job MergePDFFilesMultiOrderLabelJob
Enter fullscreen mode Exit fullscreen mode

MultiOrdersLabelPdfExport (Responsible for generating PDFs):

use Barryvdh\Snappy\Facades\SnappyPdf;

class MultiOrdersLabelPdfExport extends Jobs {
    public function __construct(public $orders) {}

    public function handle(): void {
        SnappyPdf::loadView('multi_label', ['orders' => $this->orders])
            ->setPaper('A4', 'portrait')
            ->save(public_path($this->batch()->id . '/' . microtime(true) . '.pdf'));
    }
}
Enter fullscreen mode Exit fullscreen mode

MergePDFFilesMultiOrderLabelJob (Responsible for merging PDFs):

use Webklex\PDFMerger\Facades\PDFMergerFacade;
use Illuminate\Support\Facades\File;

class MergePDFFilesMultiOrderLabelJob extends Jobs {
    public function handle(): void {
        $files = File::allFiles(public_path($this->batch()->id));

        usort($files, function ($a, $b) {
            return $a->getFilename() <=> $b->getFilename();
        });

        $merger = PDFMergerFacade::init();
        foreach ($files as $file) {
            $merger->addPDF($file->getPathname());
        }

        $merger->merge();
        $mergedFile = public_path($this->batch()->id . '/merged.pdf');
        $merger->save($mergedFile);

        // Cleanup temporary files
        foreach ($files as $file) {
            File::delete($file->getPathname());
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 6: Create the View

Create a multi_label.blade.php file under the resources/views directory for the label template:


<!DOCTYPE html>
<html>
<head>
    <style>
        body { font-family: Arial, sans-serif; font-size: 15px; }
    </style>
</head>
<body>
@foreach ($orders as $order)
    <div style="page-break-before: always;">
        <h2>Order #{{ $order->order_number }}</h2>
        <p>Order Date: {{ $order->created_at }}</p>
        <p>Total Price: ${{ $order->total_price }}</p>
        <p>Status: {{ $order->status }}</p>
    </div>
@endforeach
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

Step 7: Set Up the Controller and Route

php artisan make:controller PrintOrderLabelsController
Enter fullscreen mode Exit fullscreen mode
use App\Jobs\{MultiOrdersLabelPdfExport, MergePDFFilesMultiOrderLabelJob};
use Illuminate\Support\Facades\Bus;

class PrintOrderLabelsController extends Controller {
    public function printMultiLabels(Request $request) {
        $orderIds = $request->order_ids;
        $chunks = array_chunk($orderIds, 50);
        $jobs = [];

        foreach ($chunks as $chunk) {
            $orders = Order::whereIn('id', $chunk)->get();
            $jobs[] = new MultiOrdersLabelPdfExport($orders);
        }

        $batch = Bus::batch([...$jobs, new MergePDFFilesMultiOrderLabelJob()])->dispatch();

        return response()->json(['batch_id' => $batch->id]);
    }
}
Enter fullscreen mode Exit fullscreen mode

at function printMultiLabels()
we send order_ids at request as array to to divided big array to multi array using array_chunk and create with each array job with data of orders
to build the PDF file with that data and create batch with that jobs that created and finally execute the job that merge all files created at one file

Add the route:


Route::post('/print/multi-label', [PrintOrderLabelsController::class, 'printMultiLabels']);
Enter fullscreen mode Exit fullscreen mode

Step 8: Test the Endpoint

php artisan serve
Enter fullscreen mode Exit fullscreen mode

Use Postman to send a POST request to the /print/multi-label endpoint with a JSON body containing the order_ids array.

Image description

Step 9: Start the Queue Worker

Run the queue worker:

php artisan queue:work
Enter fullscreen mode Exit fullscreen mode

This method ensures your application processes PDF generation and merging in manageable chunks, avoiding timeouts and ensuring scalability, you can add feature when the batch is finished send email or notification with merged file link . Let me know if you need further clarification! ๐Ÿš€

Top comments (0)