تخطَّ إلى المحتوى

الاستخدام الإنتاجي لحزمة NextPDF الخاصة بـ Laravel

في بيئة الإنتاج، حُلّ عقد المستند عبر حقن المُنشئ. عالِج حالات فشل كتابة ⁨PDF⁩ باستثناء محدد النوع. انقل التوليد الثقيل أو الدُفعي إلى GeneratePdfJob، واربط استدعاءات النجاح والفشل صراحةً.

Terminal window
composer require nextpdf/laravel
php artisan vendor:publish --tag=nextpdf-config

هيّئ اتصال قائمة الانتظار في config/nextpdf.php. حدِّد queue.connection، وqueue.queue، وqueue.timeout. ثم تأكَّد من تشغيل عامل على الاتصال المُهيّأ.

يُتيح الحاوي NextPDF\Contracts\PdfDocumentInterface على شكل ربط مصنع. تنتج كل عملية حلّ NextPDF\Core\Document جديدًا. يسمح ⁨PSR-11⁩ للحاوي بإرجاع قيم مختلفة من استدعاءات get() المتتالية، بحسب استراتيجية الربط (⁨PSR-11⁩ §1.1.2). تستخدم هذه الحزمة ربط مصنع حتى لا تعبر الحالة المتغيرة محدودة النطاق بالطلب بين الطلبات أبدًا. سجلّا الخطوط والصور مفردان (⁨singletons⁩). يحفظ ذلك العقد الذي ينص على أن المعرِّف المربوط يُحلّ إلى مدخله المُسجَّل (⁨PSR-11⁩ §1.1.2)، مع الإبقاء على مشاركة الموارد المكلفة عبر العامل.

فضّل حقن المُنشئ على الواجهة (⁨facade⁩) في شِفرة الإنتاج. فهذا يجعل التبعية صريحة، ويُبقي وحدة التحكُّم قابلة لاختبار الوحدة دون تشغيل جذر الواجهة.

وحدة تحكُّم مربوطة بحقن التبعيات مع معالجة أخطاء ذات أنواع محددة

قسم بعنوان «وحدة تحكُّم مربوطة بحقن التبعيات مع معالجة أخطاء ذات أنواع محددة»
resource: NextPDF\Contracts\PdfDocumentInterface + src/Laravel/Http/PdfResponse.php
<?php
declare(strict_types=1);
namespace App\Http\Controllers;
use Illuminate\Http\Response;
use NextPDF\Contracts\PdfDocumentInterface;
use NextPDF\Laravel\Http\PdfResponse;
use Psr\Log\LoggerInterface;
use Throwable;
final class InvoiceController extends Controller
{
public function __construct(
private readonly PdfDocumentInterface $document,
private readonly LoggerInterface $logger,
) {}
public function show(int $invoiceId): Response
{
try {
$this->document->addPage();
$this->document->cell(0, 10, "Invoice #{$invoiceId}", newLine: true);
$this->document->cell(0, 10, 'Thank you for your business.');
return PdfResponse::download(
$this->document,
"invoice-{$invoiceId}.pdf",
);
} catch (Throwable $exception) {
// Rethrow as an HTTP-meaningful failure; never swallow.
$this->logger->error('Invoice PDF generation failed', [
'invoice_id' => $invoiceId,
'exception' => $exception::class,
]);
return new Response('Could not generate the invoice PDF.', 500);
}
}
}

احقن PdfDocumentInterface، لا الصنف الملموس Document، حتى تتمكن من استبدال الربط في الاختبارات. يُرجع الحاوي مستندًا جديدًا لكل تهيئة لوحدة التحكُّم. لا تُعِد استخدام النسخة نفسها من وحدة التحكُّم لمستندين غير مرتبطين ضمن العملية الواحدة.

تُسجِّل كتلة الالتقاط صنف الاستثناء وتُرجع خطأ ⁨HTTP⁩ محددًا بدلًا من تسريب تتبُّع المكدس. استخدم Psr\Log\LoggerInterface، الذي يحلّه الحاوي إلى مُسجِّل إطار العمل. يترك ⁨PSR-3⁩ تهريب النائب (⁨placeholder escaping⁩) للمُنفِّذ، ويوجّه المستدعين إلى عدم تهريب قيم السياق مسبقًا (⁨PSR-3⁩ §1.2). مرِّر سياقًا مُهيكلًا، لا سلاسل نصية مُدمَجة.

التوليد عبر قائمة الانتظار مع استدعاءات النجاح والفشل

قسم بعنوان «التوليد عبر قائمة الانتظار مع استدعاءات النجاح والفشل»

GeneratePdfJob مهمة من نوع ShouldQueue. تستخدم افتراضيًا ثلاث محاولات، ومهلة قدرها 120 ثانية، وتراجعًا قدره 10 ثوانٍ. يمكنك تجاوز القيم الثلاث كلها في config/nextpdf.php. تتلقى مُغلِّفة (⁨closure⁩) البناء المستندَ المحلول من الحاوي، ويجب أن تُرجع مستندًا مُهيّأً.

resource: src/Laravel/Jobs/GeneratePdfJob.php
<?php
declare(strict_types=1);
namespace App\Jobs;
use NextPDF\Contracts\PdfDocumentInterface;
use NextPDF\Laravel\Jobs\GeneratePdfJob;
use Psr\Log\LoggerInterface;
use Throwable;
final class DispatchMonthlyStatement
{
public function __construct(private readonly LoggerInterface $logger) {}
public function __invoke(int $accountId): void
{
// Dispatchable::dispatch() is `public static`: it constructs the
// job from the arguments it receives and returns a PendingDispatch.
// Pass every constructor argument — including the callbacks — to
// the static call. Building an instance and then calling
// `$job->dispatch(...)` would discard that instance (and its
// callbacks) and queue a different job from only the static args.
GeneratePdfJob::dispatch(
storage_path("app/statements/{$accountId}.pdf"),
static fn (PdfDocumentInterface $document): PdfDocumentInterface => $document
->addPage()
->cell(0, 10, "Statement for account {$accountId}", newLine: true),
function (string $path) use ($accountId): void {
$this->logger->info('Statement PDF written', [
'account_id' => $accountId,
'path' => $path,
]);
},
function (Throwable $exception) use ($accountId): void {
$this->logger->error('Statement PDF failed', [
'account_id' => $accountId,
'exception' => $exception::class,
]);
},
);
}
}

يُمرِّر GeneratePdfJob::dispatch() وسائطه مباشرةً إلى المُنشئ (string $outputPath, callable $builder, ?callable $onSuccess, ?callable $onFailure). ونتيجةً لذلك، تُربط استدعاءات النجاح والفشل بالمهمة نفسها التي تُوضع في قائمة الانتظار. يطابق هذا الصيغة الموضعية GeneratePdfJob::dispatch($path, $builder) في /⁨integrations/laravel/quickstart/.⁩ يتلقى استدعاء النجاح مسار الإخراج، ويتلقى استدعاء الفشل Throwable. تكشف المهمة أيضًا المُحدِّدَين الانسيابيَّين then() وcatch()، وهما يُرجعان المهمة للتسلسل. استخدم هذين المُحدِّدَين فقط عندما تحتفظ بالنسخة نفسها وتُرسلها، مثلًا عبر المساعِد dispatch(). تكشف المهمة أيضًا طريقة failed()، التي يستدعيها مُشغِّل قائمة الانتظار عند الفشل النهائي. تُغلَّف الاستدعاءات في مُغلِّفات قابلة للتسلسل (⁨serializable⁩) حتى تنجو من نقل قائمة الانتظار.

الخاصيةالافتراضيمفتاح التهيئة
tries3ليس مدفوعًا بالتهيئة؛ اشتقّ صنفًا فرعيًا للتغيير
timeout120nextpdf.queue.timeout
backoff10ليس مدفوعًا بالتهيئة؛ اشتقّ صنفًا فرعيًا للتغيير
اسم قائمة الانتظارpdfnextpdf.queue.queue
الاتصالافتراضيnextpdf.queue.connection

tries وbackoff خاصيتان عامتان تُقرآن من نسخة المهمة. لا تقرأهما المهمة المُرسلة من التهيئة. إذا اختلفت سياسة إعادة المحاولة لديك، فاشتقّ صنفًا فرعيًا من GeneratePdfJob لتجاوزهما.

  • يجب أن تُرجع مُغلِّفة البناء كائنًا من نوع PdfDocumentInterface. تحفظ المهمة قيمة الإرجاع هذه، لا النسخة التي حُلّت أصلًا. يؤكد اختبار المهمة هذا العقد صراحةً.
  • يُرجع حلّ SignerInterface القيمة null ما لم يكن التوقيع مُفعَّلًا وشهادة مُهيّأة وnextpdf/premium مثبَّتة. تحقَّق دائمًا من القيمة ⁨null⁩ قبل التوقيع.
  • تتشارك العمّال طويلة العمر (⁨Octane/RoadRunner/Swoole⁩) سجلّ الخطوط المُقفَل. هيّئ preload_fonts كي يجري الإحماء مرة واحدة عند إقلاع العامل بدلًا من أن يجري عند أول طلب.
  • تستدعي المهمة الفاشلة failed() بعد استنفاد tries. لا يستدعي فشل المحاولة الواحدة onFailure حتى يُعلن مُشغِّل قائمة الانتظار الفشل النهائي.

يحجب التوليد المتزامن في وحدة التحكُّم الطلب طوال مدة بناء ⁨PDF⁩ بالكامل. للإخراج متعدد الصفحات أو الدُفعي، أرسِل GeneratePdfJob وارجع فورًا. توزِّع السجلّات المفردة تحليل الخطوط وفك ترميز الصور على مدى عمر العامل. تنحصر التكلفة لكل طلب عندئذٍ في بناء المستند وإصدار المحتوى.

تُسجِّل وحدة التحكُّم المعتمِدة على حقن التبعيات صنف الاستثناء، لا رسالته أو تتبُّعه، لتجنُّب تسريب التفاصيل الداخلية إلى السجلّات. يتحقق GeneratePdfJob من مسار الإخراج على العامل للتخفيف من أثر الحمولات المُسلسَلة المُلاعَب بها على نقل قائمة الانتظار. التغطية الكاملة موجودة في /⁨integrations/laravel/security-and-operations/.⁩

الادعاءالمصدرالبند⁨reference_id⁩
المعرِّف المربوط يُحلّ إلى مدخله المُسجَّل⁨PSR-11 Container⁩§1.1.2
قد تختلف عمليات الحلّ المتتالية بحسب استراتيجية الربط (ربط مصنع)⁨PSR-11 Container⁩§1.1.2

يَرِد توجيه التسجيل وفق ⁨PSR-3⁩ في مواصفة ⁨PSR-3.⁩ يُسنِد ذلك التوجيه تهريب النائب إلى المُنفِّذ، ويوجّه المستدعين إلى تمرير سياق مُهيكل. انظر المستند psr_3_logger §1.2.

يستخدم إخراج ⁨PAdES B-B⁩ الموقَّع وأرشفة ⁨PDF/A⁩ عبر ⁨nextpdf/premium⁩ سطح حقن التبعيات (⁨DI⁩) نفسه. هذه قدرة ⁨Enterprise⁩ اختيارية. لا تحتاج حزمة ⁨Core⁩ الموثَّقة هنا إلى أي تغيير في الشِفرة لاعتمادها. انظر https://nextpdf.dev/get-license/?intent=laravel-signing.

  • /⁨integrations/laravel/quickstart/⁩ — أبسط مثال أولي
  • /⁨integrations/laravel/configuration/⁩ — مفاتيح قائمة الانتظار والتوقيع والخطوط
  • /⁨integrations/laravel/security-and-operations/⁩ — نموذج التهديد والتقوية
  • /⁨integrations/laravel/troubleshooting/⁩ — حالات فشل الإنتاج الشائعة