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
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
Step 2: Create a Model and Migration
php artisan make:model Order -m
php artisan make:queue-batches-table
php artisan migrate
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',
];
}
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');
}
};
Run the migration:
php artisan migrate
Step 3: Add Dummy Data
Use Laravel seeders to add to the orders table with dummy data for testing:
php artisan make:seeder OrderSeeder
Step 4: Install Required Packages
Install the packages for generating and merging PDF files:
composer require barryvdh/laravel-snappy
composer require webklex/laravel-pdfmerger
Step 5: Create the Job Classes
Generate two job classes:
php artisan make:job MultiOrdersLabelPdfExport
php artisan make:job MergePDFFilesMultiOrderLabelJob
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'));
}
}
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());
}
}
}
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>
Step 7: Set Up the Controller and Route
php artisan make:controller PrintOrderLabelsController
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]);
}
}
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']);
Step 8: Test the Endpoint
php artisan serve
Use Postman to send a POST request to the /print/multi-label endpoint with a JSON body containing the order_ids array.
Step 9: Start the Queue Worker
Run the queue worker:
php artisan queue:work
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)