W tym artykule porównujemy popularne rozwiązania do generowania PDF w Laravel.
📋 Spis treści
Wprowadzenie
Ten artykuł prezentuje implementację dwóch popularnych rozwiązań do generowania PDF-ów w Laravel: Spatie Laravel PDF (z Browsershot) oraz barryvdh/laravel-dompdf. Oba rozwiązania mają swoje mocne strony i ograniczenia - poniżej znajdziesz instrukcje konfiguracji, przykłady użycia, porównanie oraz rekomendacje, kiedy używać każdego z nich.
Instalacja i konfiguracja
1. Spatie Laravel PDF (Browsershot)
- Instalacja pakietu:
composer require spatie/laravel-pdf npm install puppeteer npx puppeteer browsers install chrome
- Upewnij się, że Chromium jest zainstalowany w obrazie Docker PHP (jeśli używasz Dockera):
RUN apt-get update \ && apt-get install -y wget gnupg2 \ && wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - \ && echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" > /etc/apt/sources.list.d/google-chrome.list \ && apt-get update \ && apt-get install -y google-chrome-stable \ && apt-get clean \ && rm -rf /var/lib/apt/lists/*
2. barryvdh/laravel-dompdf
- Instalacja pakietu:
composer require barryvdh/laravel-dompdf php artisan vendor:publish --provider="Barryvdh\DomPDF\ServiceProvider"
- Nie wymaga dodatkowych zależności systemowych. Stylowanie ograniczone do podstawowego CSS.
Przykładowe trasy i kontroler
W routes/web.php
:
Route::get('/pdf/spatie', [PdfDemoController::class, 'spatie'])->name('pdf.spatie');
Route::get('/pdf/dompdf', [PdfDemoController::class, 'dompdf'])->name('pdf.dompdf');
W app/Http/Controllers/PdfDemoController.php
:
<?php
declare(strict_types=1);
namespace App\Http\Controllers;
use Barryvdh\DomPDF\Facade\Pdf as DomPdf;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Spatie\Browsershot\Browsershot;
use Spatie\LaravelPdf\Facades\Pdf;
class PdfDemoController extends Controller
{
public function spatie(Request $request)
{
$data = [
'user' => [
'name' => 'Jan Kowalski',
'email' => '[email protected]',
],
'items' => [
['name' => 'Super Produkt', 'qty' => 2, 'price' => 199.99],
['name' => 'Mega Usługa', 'qty' => 1, 'price' => 499.00],
],
'total' => 199.99 * 2 + 499.00,
];
return Pdf::view('pdfs.spatie-invoice', $data)
->withBrowsershot(function (Browsershot $browsershot): void {
$browsershot->noSandbox()->format('A4');
})
->download('faktura-spatie.pdf');
}
public function dompdf(Request $request): Response
{
$data = [
'invoice_number' => 'FV/2024/05/001',
'date' => now()->toDateString(),
'client' => 'Anna Nowak',
'items' => [
['name' => 'Produkt A', 'qty' => 3, 'price' => 100],
['name' => 'Produkt B', 'qty' => 1, 'price' => 250],
],
'total' => 3 * 100 + 250,
];
return DomPdf::loadView('pdfs.dompdf-invoice', $data)->download('faktura-dompdf.pdf');
}
}
Przykładowe widoki
Spatie Laravel PDF (Tailwind, nowoczesny układ)
resources/views/pdfs/layouts/pdf.blade.php
:
<!DOCTYPE html>
<html lang="pl">
<head>
<meta charset="UTF-8">
<title>@yield('title', 'PDF')</title>
<style>
body { font-family: 'Inter', sans-serif; }
</style>
@yield('styles')
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="bg-gray-100 p-8">
@yield('content')
</body>
</html>
resources/views/pdfs/spatie-invoice.blade.php
:
@extends('pdfs.layouts.pdf')
@section('title', 'Faktura (Spatie Laravel PDF)')
@section('content')
<div class="max-w-2xl mx-auto bg-white rounded shadow p-8">
<div class="flex justify-between items-center mb-8">
<div>
<h1 class="text-2xl font-bold text-gray-800">FAKTURA</h1>
<p class="text-gray-500">Sprzedawca: <span class="font-semibold">{{ $user['name'] }}</span></p>
<p class="text-gray-500">Email: {{ $user['email'] }}</p>
</div>
<div class="text-right">
<p class="text-gray-500">Data: {{ now()->toDateString() }}</p>
<p class="text-gray-500">Nr faktury: FV/{{ now()->format('Y') }}/{{ now()->format('m') }}/001</p>
</div>
</div>
<table class="w-full mb-8 text-sm border">
<thead>
<tr class="bg-gray-200">
<th class="p-2 border">#</th>
<th class="p-2 border text-left">Produkt</th>
<th class="p-2 border">Ilość</th>
<th class="p-2 border">Cena jedn.</th>
<th class="p-2 border">Razem</th>
</tr>
</thead>
<tbody>
@foreach($items as $i => $item)
<tr class="border-b">
<td class="p-2 border text-center">{{ $i+1 }}</td>
<td class="p-2 border">{{ $item['name'] }}</td>
<td class="p-2 border text-center">{{ $item['qty'] }}</td>
<td class="p-2 border text-right">{{ number_format($item['price'], 2) }} zł</td>
<td class="p-2 border text-right">{{ number_format($item['qty'] * $item['price'], 2) }} zł</td>
</tr>
@endforeach
</tbody>
<tfoot>
<tr>
<td colspan="4" class="p-2 border text-right font-bold">Suma</td>
<td class="p-2 border text-right font-bold">{{ number_format($total, 2) }} zł</td>
</tr>
</tfoot>
</table>
<div class="text-xs text-gray-400 mt-8">Wygenerowano automatycznie przez Spatie Laravel PDF + TailwindCSS</div>
</div>
@endsection
DomPDF (prosty układ)
resources/views/pdfs/dompdf-invoice.blade.php
:
@extends('pdfs.layouts.pdf')
@section('title', 'Faktura (DomPDF)')
@section('styles')
<style>
body { font-family: DejaVu Sans, sans-serif; }
table { border-collapse: collapse; width: 100%; margin-bottom: 20px; }
th, td { border: 1px solid #ccc; padding: 6px 10px; }
th { background: #eee; }
.text-right { text-align: right; }
.text-center { text-align: center; }
</style>
@endsection
@section('content')
<body>
<h2>FAKTURA (DomPDF)</h2>
<p>Nr faktury: <strong>{{ $invoice_number }}</strong></p>
<p>Data: <strong>{{ $date }}</strong></p>
<p>Klient: <strong>{{ $client }}</strong></p>
<table>
<thead>
<tr>
<th>#</th>
<th>Produkt</th>
<th>Ilość</th>
<th>Cena jedn.</th>
<th>Razem</th>
</tr>
</thead>
<tbody>
@foreach($items as $i => $item)
<tr>
<td class="text-center">{{ $i+1 }}</td>
<td>{{ $item['name'] }}</td>
<td class="text-center">{{ $item['qty'] }}</td>
<td class="text-right">{{ number_format($item['price'], 2) }} zł</td>
<td class="text-right">{{ number_format($item['qty'] * $item['price'], 2) }} zł</td>
</tr>
@endforeach
</tbody>
<tfoot>
<tr>
<td colspan="4" class="text-right"><strong>Suma</strong></td>
<td class="text-right"><strong>{{ number_format($total, 2) }} zł</strong></td>
</tr>
</tfoot>
</table>
<p style="font-size: 10px; color: #888;">Wygenerowano automatycznie przez barryvdh/laravel-dompdf</p>
</body>
@endsection
Porównanie i rekomendacje
Funkcja | Spatie Laravel PDF (Browsershot) | barryvdh/laravel-dompdf |
---|---|---|
Silnik | Headless Chrome (Puppeteer) | DomPDF (PHP) |
Wymaga Chrome/Chromium | Tak | Nie |
Stylowanie | Pełny CSS, Tailwind, grid, flexbox | Podstawowy CSS |
Nowoczesne układy | Tak | Ograniczone |
Jakość renderowania | Doskonała | Dobra/Średnia |
Szybkość | Wolniejsza (uruchamianie Chrome) | Szybka (czysty PHP) |
Wsparcie dla JS | Tak (ograniczone) | Nie |
Wsparcie dla SVG | Tak | Ograniczone |
Złożoność konfiguracji | Średnia (Docker, Node, Chrome) | Bardzo prosta |
Kompatybilność z Docker/CI | Tak (z noSandbox) | Tak |
Kiedy używać?
- Spatie Laravel PDF (Browsershot): Gdy potrzebujesz pięknych, nowoczesnych PDF-ów, chcesz używać Tailwind, grid, flexbox, SVG, a Twoje środowisko wspiera Chrome/Chromium (np. Docker, dedykowany serwer). Użyj
withBrowsershot()
, aby dostosować opcje dla Dockera/CI. - barryvdh/laravel-dompdf: Gdy potrzebujesz prostych PDF-ów, chcesz szybkiej konfiguracji i nie potrzebujesz zaawansowanych funkcji CSS lub JS (np. proste faktury, potwierdzenia).
Podsumowanie
Oba rozwiązania mają swoje miejsce w ekosystemie Laravel. Dla nowoczesnych projektów, gdzie liczy się design i elastyczność układu, wybierz Spatie Laravel PDF (z Browsershot). Dla prostych przypadków użycia, gdzie kluczowa jest szybkość i prostota, barryvdh/laravel-dompdf jest wystarczające.
Śledź mnie na LinkedIn, aby otrzymywać więcej wskazówek o Laravel i DevOps!
Czy chciałbyś dowiedzieć się więcej o generowaniu PDF-ów w Laravel? Daj znać w komentarzach poniżej!