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

الاستخدام في الإنتاج مع CodeIgniter 4

تتلقى متحكمات الإنتاج خدمات ⁨NextPDF⁩ المُجسَّدة. وهي تعالج تسلسل الاستثناءات المُوثَّق صراحةً وتُصدر إشارات قابلية الرصد. انقل أعمال إنشاء المستندات المحمولة (⁨PDF⁩) الطويلة الأمد خارج الطلب عبر ⁨CodeIgniter 4 Queue.⁩

يحلّ ⁨CodeIgniter 4⁩ خدمات الحزمة عبر مُحدِّد المواقع الخاص به. في نمط مُحدِّد الخدمات، يتلقى الكائن حاوية ويستخدمها لاسترجاع تبعياته بنفسه. تنصح إرشادات توصية ⁨PHP⁩ المعيارية (⁨PSR⁩) بتجنّب هذا النمط (⁨PSR-11⁩ §1.3، الصيغة الوجوبية ⁨SHOULD NOT⁩). لاتّباع هذه الإرشادات، حلّ كل خدمة من خدمات ⁨NextPDF⁩ مرة واحدة عند حدود المتحكم، ثم مرّر الكائن المُجسَّد إلى الداخل. لا تمرّر صنف Services أو أي حاوية إلى شيفرة المجال لديك.

يضع كل مثال ⁨PHP⁩ declare(strict_types=1); في سطر مستقل (⁨PSR-12⁩ §⁨x1.x3.p34⁩).

مهمة الإنتاجالسطح المُتحقَّق منه
حلّ الخدماتServices::pdf(false)، Services::pdfDocument(false)، Services::documentFactory()
بناء الاستجابةPdfResponse::download() / inline()DownloadResponse
التقاط حالات الفشلNextPDF\Exception\NextPdfException (النوع الأساسي للنظام البيئي)
التوليد غير المتزامنGeneratePdfJob مُسجَّلة في Config\Queue::$jobHandlers
حواجز المسار / القابل للاستدعاءGeneratePdfJob تُطلق InvalidArgumentException

متحكم الإنتاج — معالجة الأخطاء وقابلية الرصد

قسم بعنوان «متحكم الإنتاج — معالجة الأخطاء وقابلية الرصد»

يطلق محرّك النواة استثناءات تمتدّ جميعها من NextPDF\Exception\NextPdfException. التقط هذا النوع الواحد لتغطية حالات فشل النواة والامتدادات. تُسجّل كتلة الالتقاط هذه السياق وتُعيد استجابة خطأ مُحدَّدة، ولا تكون أبداً كتلة التقاط فارغة.

<?php
declare(strict_types=1);
namespace App\Controllers;
use CodeIgniter\HTTP\DownloadResponse;
use CodeIgniter\HTTP\ResponseInterface;
use NextPDF\CodeIgniter\Config\Services;
use NextPDF\Exception\NextPdfException;
use Psr\Log\LoggerInterface;
final class InvoiceController extends BaseController
{
public function download(int $id): DownloadResponse|ResponseInterface
{
/** @var LoggerInterface $logger */
$logger = \service('logger');
$start = \hrtime(true);
try {
$pdf = Services::pdf(false);
$pdf->document()->addPage();
$pdf->document()->cell(0, 10, "Invoice #{$id}");
$response = $pdf->download("invoice-{$id}.pdf");
$logger->info('pdf.invoice.generated', [
'invoice_id' => $id,
'elapsed_ms' => (\hrtime(true) - $start) / 1_000_000,
]);
return $response;
} catch (NextPdfException $e) {
$logger->error('pdf.invoice.failed', [
'invoice_id' => $id,
'exception' => $e::class,
'message' => $e->getMessage(),
]);
return $this->response
->setStatusCode(ResponseInterface::HTTP_INTERNAL_SERVER_ERROR)
->setJSON(['error' => 'pdf_generation_failed', 'invoice_id' => $id]);
}
}
}

تُعيد Services::pdf(false) مكتبة جديدة ومستنداً أساسياً جديداً في كل استدعاء. لا تتشارك الطلبات المتزامنة أبداً حالة المستند. تؤكّد الاختبارات الوظيفية للحزمة هذا السلوك.

أعمار الخدمات الآمنة للعامل

قسم بعنوان «أعمار الخدمات الآمنة للعامل»

سجلّا الخطوط والصور كائنان أحاديّان بعمر العملية حسب التصميم. يُهيَّأ سجلّ الخطوط ويُقفَل مرة واحدة. أما سجلّ الصور فهو ذاكرة تخزين مؤقت محدودة من نوع الأقل استخداماً مؤخراً (⁨LRU⁩). في عامل طويل الأمد (خادم ⁨CodeIgniter spark⁩، أو مشغِّل بأسلوب ⁨RoadRunner⁩، أو عامل قائمة انتظار)، يكون هذا السلوك مقصوداً: تبقى السجلّات المكلفة، بينما يكون كل مستند جديداً. لا تطلب مستنداً مشتركاً (Services::pdfDocument(true)) في شيفرة الطلب أو المهمة؛ فهو موجود فقط لإعادة تعيين الاختبار، وسيتشارك المحتوى عبر الطلبات.

التوليد غير المتزامن باستخدام ⁨CodeIgniter Queue⁩

قسم بعنوان «التوليد غير المتزامن باستخدام ⁨CodeIgniter Queue⁩»

تُشغِّل GeneratePdfJob توليد ⁨PDF⁩ خارج الطلب عبر codeigniter4/queue. يتطلب زمن تشغيل قائمة الانتظار إعدادَين. اضبطهما كليهما بشكل صحيح.

1. سجّل معالج المهمة بالاسم

قسم بعنوان «1. سجّل معالج المهمة بالاسم»

تحلّ قائمة الانتظار المهمة عبر مفتاح اسم، لا عبر سلسلة صنف. يتحقق معالج قائمة الانتظار من اسم المهمة المدفوعة بمقارنته مع مفاتيح Config\Queue::$jobHandlers. وترفض الاسم غير المعروف عبر CodeIgniter\Queue\Exceptions\QueueException. سجّل المهمة في app/Config/Queue.php:

<?php
declare(strict_types=1);
namespace Config;
use CodeIgniter\Queue\Config\Queue as BaseQueue;
use NextPDF\CodeIgniter\Jobs\GeneratePdfJob;
final class Queue extends BaseQueue
{
/** @var array<string, class-string> */
public array $jobHandlers = [
'generate-pdf' => GeneratePdfJob::class,
];
}

2. أرسل عبر الاسم المُسجَّل

قسم بعنوان «2. أرسل عبر الاسم المُسجَّل»

ادفع المهمة مع الاسم المُسجَّل كوسيط ثانٍ. الوسيط الأول هو اسم قائمة الانتظار، والوسيط الثالث هو مصفوفة بيانات المهمة.

<?php
declare(strict_types=1);
namespace App\Controllers;
use CodeIgniter\HTTP\ResponseInterface;
final class InvoiceController extends BaseController
{
public function queueInvoice(int $id): ResponseInterface
{
\service('queue')->push('pdf-queue', 'generate-pdf', [
'builder' => 'App\\PdfBuilders\\InvoiceBuilder::build',
'outputPath' => WRITEPATH . 'pdfs/invoice-' . $id . '.pdf',
'context' => ['invoice_id' => $id],
]);
return $this->response
->setStatusCode(ResponseInterface::HTTP_ACCEPTED)
->setJSON(['status' => 'queued', 'invoice_id' => $id]);
}
}

3. نفّذ الباني ضمن ⁨App⁩\⁨PdfBuilders⁩

قسم بعنوان «3. نفّذ الباني ضمن ⁨App⁩\⁨PdfBuilders⁩»

تسمح المهمة بالبناة القابلين للاستدعاء فقط ضمن مساحة الأسماء App\PdfBuilders وتحصر مسارات الإخراج في WRITEPATH/pdfs/. نفّذ الباني كطريقة ساكنة. يتلقى Document جديداً ومصفوفة السياق، ثم يعيد المستند.

<?php
declare(strict_types=1);
namespace App\PdfBuilders;
use NextPDF\Core\Document;
final class InvoiceBuilder
{
/** @param array<string, mixed> $context */
public static function build(Document $document, array $context): Document
{
$invoiceId = (int) ($context['invoice_id'] ?? 0);
$document->addPage();
$document->cell(0, 10, "Invoice #{$invoiceId}");
return $document;
}
}
Terminal window
php spark queue:work pdf-queue

يبدأ كل تشغيل للمهمة بمستند جديد من Services::pdfDocument(). يطبّق الباني، ثم يحفظ في المسار المُتحقَّق منه. تتحقق اختبارات الحزمة من أن تشغيلَين متتاليين للمهمة لا يتشاركان حالة المستند.

  • ترفض قائمة الانتظار GeneratePdfJob::class كاسم للمهمة عند الدفع لأنه ليس المفتاح المُسجَّل 'generate-pdf'. ادفع دائماً مفتاح jobHandlers.
  • يجب أن تطابق سلسلة الباني App\PdfBuilders\<Class>::<method> تماماً. الدوال، أو مساحات الأسماء الأخرى، أو الحمولات ذات البادئة أو اللاحقة تُطلق InvalidArgumentException قبل تشغيل أي شيفرة.
  • يجب أن يُحلّ مسار الإخراج داخل WRITEPATH/pdfs/ وأن ينتهي بـ .pdf (غير حساس لحالة الأحرف). تُرفض مسارات الاجتياز والبادئة الشقيقة.
  • codeigniter4/queue هي تبعية مخصصة للتطوير فقط للحزمة. اطلبها في التطبيق الذي يشغّل العمّال.

تُنشأ السجلّات مرة واحدة لكل عملية عامل. تتناسب تكلفة بناء المستند مع المحتوى، لا مع المُحوِّل. للمهام الدُّفعية الكبيرة، استخدم مسار قائمة الانتظار حتى يبقى عمّال الطلبات سريعي الاستجابة. عيّن performance_budget في أي وصفة لها هدف قابل للقياس.

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

  • تتلقى المتحكمات خدمات مُجسَّدة، لا حاوية، بما يتوافق مع إرشادات مُحدِّد الخدمات في ⁨PSR-11⁩ §1.3.

نواة ⁨NextPDF⁩ مرخّصة بموجب ⁨Apache-2.0.⁩ لإنتاج مخرجات مُوقَّعة ومخرجات ⁨PDF/A⁩ في مهام قائمة الانتظار، ثبّت ⁨NextPDF Pro⁩ أو ⁨Enterprise⁩ في بيئة العامل. تكشف حزمة ⁨CodeIgniter⁩ طرق الخدمة المقابلة. وتُعيد null إلى أن تُثبَّت حزمة ⁨Premium⁩ المطابقة. انظر </get-license/?intent=codeigniter-async-signing>.

  • /⁨integrations/codeigniter/quickstart/⁩ — النسخة المختصرة من هذه المتحكمات.
  • /⁨integrations/codeigniter/configuration/⁩ — التوقيع، وسلطة الطوابع الزمنية (⁨TSA⁩)، وضبط المسار.
  • /⁨integrations/codeigniter/security-and-operations/⁩ — نموذج تهديد قائمة الانتظار والتعزيز الأمني.
  • /⁨integrations/codeigniter/troubleshooting/⁩ — أنماط فشل قائمة الانتظار والاكتشاف.
  • /⁨integrations/codeigniter/integration/⁩ — مرجع التوصيل واختبار الدخان.