تحويل HTML إلى PDF باستخدام محرك Artisan Chrome
لمحة سريعة
قسم بعنوان «لمحة سريعة»يعرض جسر Artisan محتوى HTML عبر عملية Chrome بلا واجهة رسومية، ثم يستورد النتيجة إلى مستند NextPDF ككائن Form XObject متجهي. يبقى النص قابلاً للتحديد والبحث بدلاً من تحويله إلى صورة نقطية. أرفِق ChromeRendererConfig، واستدعِ writeHtmlChrome() على مستند، أو استخدم ChromeHtmlRenderer مباشرة، واترك تخطيط الصفحة لـ Chrome. يغطي هذا الدليل استدعاء العرض وعزل الشبكة وضبط حجم الصفحة وارتفاع المحتوى ودورة حياة محرك العرض طويلة الأمد داخل عامل واحد.
ابدأ بالمتطلبات المسبقة:
- نواة NextPDF و
nextpdf/artisanمثبَّتان. - ملف تنفيذي لـ Chrome أو Chromium مثبَّت، ويمكن لمستخدم العامل تشغيله بلا واجهة رسومية. تحقَّق من ذلك باستخدام
chromium --headless --dump-dom about:blankقبل أن تبدأ. تغطي صفحة إعداد محرك عرض Chrome المرتبطة ضمن “انظر أيضاً” تجهيز الملف التنفيذي وقرار استخدام الحاوية المعزولة.
يفترض هذا الدليل العملي أنك تستطيع تشغيل عملية Chrome بالقرب من التطبيق. للحصول على أول مثال قابل للتشغيل، اقرأ دليل البدء السريع لـ Artisan.
التثبيت
قسم بعنوان «التثبيت»ثبِّت الجسر بجانب النواة.
composer require nextpdf/artisanثبِّت إصدار Chrome أو Chromium يمكن لمستخدم العامل تشغيله. على Debian أو Ubuntu، استخدم حزمة التوزيعة.
apt-get install -y chromiumتأكَّد من أن الملف التنفيذي يعمل بلا واجهة رسومية بصفة مستخدم العامل.
chromium --headless --dump-dom about:blankرمز الخروج 0 مع نموذج كائنات مستند (DOM) فارغ يعني أن الملف التنفيذي ومكتباته المشتركة موجودة. رمز الخروج غير الصفري هو الفشل نفسه الذي يُظهره الجسر على هيئة ChromeRenderException. أصلِحه هنا أولاً.
نظرة عامة مفاهيمية
قسم بعنوان «نظرة عامة مفاهيمية»writeHtmlChrome() هي طريقة في Document ضمن نواة NextPDF. تتحقق من صحة المُدخَل، وتحلّ محرك عرض Artisan، وترسل HTML إلى Chrome عبر بروتوكول Chrome DevTools (CDP)، وتحلّل ملف PDF المُعاد، وتُضمِّن الصفحة 0 ككائن Form XObject عند موضع المؤشر الحالي. يعمل Chrome كعملية فرعية لعامل PHP. يقود الجسر Chrome عبر CDP بدلاً من الاتصال بعملية Chrome منفصلة عبر منفذ تصحيح، لذلك لا توجد أي نقطة وصول شبكية يجب كشفها أو التحقق من هويتها.
يعرض الجسر في وضع شبكة يحظر كل شيء افتراضياً. يستخدم كل عرض سياسة أمان محتوى (Content-Security-Policy) تمنع كل أصول الموارد (default-src 'none') ولا تسمح إلا بالصور المضمَّنة (img-src data:). كما يحظر الجسر كل عنوان URL لمورد فرعي على مستوى نقل CDP باستخدام Network.setBlockedURLs(['*']). ونتيجة لذلك، لا تُحمَّل أي صورة أو ورقة أنماط أو خط أو سكربت أو iframe بعيد في HTML لديك. ضمِّن كل أصل على هيئة معرِّف موارد موحَّد (URI) من نوع data:. بهذه الطريقة يعالج الجسر خطر تزوير الطلب من جانب الخادم (SSRF) عندما يعرض HTML قد لا يكون موثوقاً، وينطبق ذلك بغض النظر عن أي إعداد.
يدعم نموذج حجم الصفحة وضعين. عندما تزوّد كلاً من العرض والارتفاع، بوحدات نقاط PDF، يطبع Chrome بحجم الورق هذا بالضبط. عند حذف الارتفاع أو ضبطه على null، يقيس الجسر ارتفاع المحتوى المعروض في Chrome، ويحوّله إلى نقاط، ويضيف هامش أمان صغيراً لإعادة التدفق يقارب 14.4 نقطة. يمنع ذلك printToPDF من التدفق إلى صفحة ثانية يقتطعها المستورِد الذي لا يتعامل إلا مع الصفحة 0.
واجهة البرمجة (API)
قسم بعنوان «واجهة البرمجة (API)»// On a NextPDF core Document (the HasTextOutput concern):writeHtmlChrome(string $html, ?float $width = null, ?float $height = null): static
// The standalone renderer:new ChromeHtmlRenderer(ChromeRendererConfig $config, ?LoggerInterface $logger = null)ChromeHtmlRenderer::render(string $html, float $widthPt, float $heightPt = 0.0): ChromeRenderResultChromeHtmlRenderer::close(): void
// The configuration value object (final readonly):new ChromeRendererConfig( ?string $chromeBinaryPath = null, int $renderTimeout = 30, string $defaultCss = '', int $maxHtmlSize = 5_000_000, bool $noSandbox = false,)ChromeRendererConfig::fromArray(array $config): selfChromeRendererConfig هي واجهة الإعداد الوحيدة. وهي غير قابلة للتغيير، لذا أنشئ نسخة جديدة لتغيير أي قيمة. ChromeRenderResult::getPdfData() تُرجع بايتات PDF. تسرد صفحة إعداد Artisan المرتبطة ضمن “انظر أيضاً” مرجع الخيارات الكامل وأعلام تشغيل Chrome الثابتة.
مثال برمجي — البدء السريع
قسم بعنوان «مثال برمجي — البدء السريع»أرفِق الإعداد بمستند، واعرض HTML موثوقاً به، ثم احفظ.
<?php
declare(strict_types=1);
require __DIR__ . '/vendor/autoload.php';
use NextPDF\Artisan\ChromeRendererConfig;use NextPDF\Core\Document;
$config = new ChromeRendererConfig( chromeBinaryPath: '/usr/bin/chromium',);
$document = Document::createStandalone();$document->setChromeRendererConfig($config);$document->addPage();
$document->writeHtmlChrome(' <div style="display: flex; gap: 20px; font-family: sans-serif;"> <div style="flex: 1; background: #f0f0f0; padding: 24px;"> <h2>Revenue</h2> <p style="font-size: 2em; color: #2563eb;">$124,500</p> </div> <div style="flex: 1; background: #f0f0f0; padding: 24px;"> <h2>Orders</h2> <p style="font-size: 2em; color: #16a34a;">1,847</p> </div> </div>');
$document->save('/tmp/report.pdf');يتولى Chrome تخطيط flex، وتبقى الأرقام قابلة للتحديد في الناتج لأن الصفحة مُضمَّنة ككائن Form XObject متجهي، لا كصورة نقطية. لملاءمة صفحة A4 ثابتة، مرِّر العرض والارتفاع بالنقاط.
$document->writeHtmlChrome($html, width: 595.28, height: 841.89);مثال برمجي — الإنتاج
قسم بعنوان «مثال برمجي — الإنتاج»في بيئة الإنتاج، أنشئ محرك عرض واحداً لكل عامل، واحقن مسجِّلاً متوافقاً مع PSR-3، والتقط كل نوع من نوعَي الاستثناء المختلفين على حدة، وحرِّر عملية Chrome بشكل حتمي عند الإيقاف.
<?php
declare(strict_types=1);
use NextPDF\Artisan\ChromeHtmlRenderer;use NextPDF\Artisan\ChromeRendererConfig;use NextPDF\Artisan\Exception\ChromeNotAvailableException;use NextPDF\Artisan\Exception\ChromeRenderException;use Psr\Log\LoggerInterface;
final class ReportRenderer{ private ChromeHtmlRenderer $renderer;
public function __construct(LoggerInterface $logger) { $config = ChromeRendererConfig::fromArray([ 'chrome_binary' => getenv('CHROME_BINARY') ?: null, 'render_timeout' => 45, 'max_html_size' => 2_000_000, 'no_sandbox' => (bool) getenv('CHROME_NO_SANDBOX'), ]);
$this->renderer = new ChromeHtmlRenderer($config, $logger); }
public function render(string $html, float $widthPt, float $heightPt = 0.0): string { try { return $this->renderer->render($html, $widthPt, $heightPt)->getPdfData(); } catch (ChromeNotAvailableException $exception) { // Deployment fault: the Chrome runtime is missing. Page on-call. throw $exception; } catch (ChromeRenderException $exception) { // Render-time fault: timeout, crash, or empty output. Retryable once. throw $exception; } }
public function shutdown(): void { $this->renderer->close(); }}أنشئ محرك العرض مرة واحدة، ثم أعد استخدامه. يحافظ تجمُّع المتصفح الداخلي على عملية Chrome واحدة قيد التشغيل، ويعيد تشغيلها كل 100 عملية عرض للحد من نمو الذاكرة. تفصل كتلتا الالتقاط بين عطل في النشر، مثل وقت تشغيل مفقود، وعطل في وقت العرض يمكنك إعادة محاولته مرة واحدة. لا تترك أي كتلة التقاط فارغة. استدعِ shutdown() عند إيقاف العامل لتحرير عملية Chrome بدلاً من انتظار المُتلِف.
أنشئ الإعداد من مصفوفة إعدادات إطار العمل لاستخدام مفاتيح snake-case، وثبِّت chromeBinaryPath في بيئة الإنتاج ليكون الملف التنفيذي حتمياً.
الحالات الحدية والمزالق
قسم بعنوان «الحالات الحدية والمزالق»- HTML الفارغ لا يفعل شيئاً.
writeHtmlChrome('')تُعيد المستند دون تغيير. - لا توجد صفحة بعد. إذا لم يكن للمستند أي صفحة، تضيف
writeHtmlChrome()واحدة قبل العرض. - الأصول البعيدة لا تُحمَّل — بحكم التصميم.
<img src="https://...">تُعرَض فارغة. ضمِّن كل أصل على هيئة معرِّف موارد موحَّد (URI) من نوعdata:. هذه هي وضعية عزل الشبكة، وليست عيباً. - لا تُستورَد إلا الصفحة 0. يضيف الارتفاع التلقائي للملاءمة هامش إعادة التدفق بحيث تُنتَج صفحة واحدة. مع ارتفاع صريح، لا يُضاف أي هامش ويطابق الناتج حجم الورق المطلوب بالضبط، لذا حدِّد الارتفاع ليلائم محتواك.
- الجسر مفقود. إذا لم يكن
nextpdf/artisanمثبَّتاً، تثير النواة استثناء إعداد بدلاً من خطأ فادح. إذا كانت مكتبةchrome-php/chromeغائبة، يثير الجسرChromeNotAvailableExceptionمصحوباً بأمر التثبيت. defaultCssو</style>. يُزال أي تسلسل</style>فيdefaultCssقبل حقنه دفاعاً ضد كسر الأنماط. خطِّط لذلك إن كنت تُنشئ CSS من قوالب.
الأداء
قسم بعنوان «الأداء»يتحمل العرض الأول تكلفة بدء تشغيل Chrome والتخطيط. تعيد عمليات العرض اللاحقة استخدام عملية Chrome الحية، فنادراً ما تتحمل تكلفة بدء التشغيل. أنشئ محرك عرض واحداً لكل عامل وأعد استخدامه. لا تنشئ واحداً لكل طلب. توقَّع ارتفاعاً في زمن الاستجابة عند عملية العرض رقم 100، حين يعيد الجسر تشغيل عملية Chrome للحد من الذاكرة. احسب حساب ذلك في أهداف زمن الاستجابة بدلاً من معاملته كحادثة. اقرِن renderTimeout بميزانية زمنية أعلى للطلب على امتداد السلسلة في أي مسار يمكن بلوغه عبر مُدخَل غير موثوق.
ملاحظات أمنية
قسم بعنوان «ملاحظات أمنية»- عزل الشبكة هو الضابط الأساسي. لا يسمح الجسر بأي جلب لمورد فرعي صادر إطلاقاً: سياسة أمان المحتوى
default-src 'none'إضافة إلى حظر كل عنوان URL على مستوى نقل CDP. وهو لا يطبّق قائمة سماح بالنطاقات لأنه لا يحتاج إليها. ضمِّن الأصول على هيئة معرِّفات موارد موحَّدة (URI) من نوعdata:. - يُقيَّد المُدخَل قبل الاتصال بـ Chrome. يرفض الجسر HTML الذي يتجاوز
maxHtmlSize(5 MB افتراضياً)، ومعرِّف موارد موحَّداً (URI) من نوع base64 إذا كان مفرط الحجم (حماية من قنبلة فك الضغط)، وأي وسم<meta http-equiv="refresh">(قد يدفع إلى التنقل نحو نقطة وصول داخلية). أبقِmaxHtmlSizeعلى القيمة الافتراضية ما لم يتطلب حِمل عمل معروف قيمة أكبر. إن رفعها يوسِّع سطح استنزاف الموارد. - حاوية Chrome المعزولة (sandbox) ضابط منفصل. يؤدي ضبط
noSandbox: trueإلى تشغيل Chrome مع--no-sandbox، ما يزيل عزل عملية Chrome. هذا يقلل الاحتواء فعلياً، وليس مجرد عَلَم شكلي. أبقِهfalseخارج الحاويات. عندما يتعذر تهيئة حاوية النظام المعزولة، شغِّل Chrome بصفة مستخدم غير جذري في حاوية مقيَّدة، وتعامل مع هذا النشر على أنه يتطلب ثقة أعلى بالمُدخَل. - السجلات تحمل البيانات الوصفية فقط. احقن مسجِّلاً متوافقاً مع PSR-3. يسجِّل الجسر أطوال البايتات والأبعاد وأحداث دورة الحياة، ولا يسجِّل أبداً HTML أو بايتات PDF أو النص المُستخرَج.
- لا تكشف أبداً منفذ تصحيح Chrome عن بُعد. لا يستخدم الجسر أي منفذ، ومنفذ CDP المفتوح هو قناة تحكم غير مُتحقَّق من هويتها.
يوجد نموذج التهديد الكامل، بما في ذلك دفاع SSRF وحدود الحاوية المعزولة الصريحة وفهرس أنماط الفشل، في صفحة أمان وعمليات Artisan المرتبطة ضمن “انظر أيضاً”. توثّق تلك الصفحة بنود OWASP وCWE وNIST ذات الصلة.
المطابقة
قسم بعنوان «المطابقة»لا يقدّم هذا الدليل أي ادعاء معياري بشأن المواصفات بذاته. تربط صفحة أمان وعمليات Artisan الأصلية ضوابط الشبكة والعزل واستنزاف الموارد في الجسر بمعيار OWASP ASVS، وبأبرز 25 من CWE (SSRF / استهلاك موارد غير منضبط)، وبـ NIST SP 800-53 SC-7. تعيد صفحة دليل الطبخ هذه ذكر طريقة الاستخدام وتُحيل تلك الاستشهادات المعيارية إلى تلك الصفحة. لا ينفّذ الجسر أي عملية تشفيرية؛ فالتوقيع والتشفير من شؤون النواة أو الإصدار التجاري ولا يتأثران بـ Artisan.
انظر أيضاً
قسم بعنوان «انظر أيضاً»- العرض على الحافة باستخدام Cloudflare — اعرض HTML على الحافة مع بديل محلي.
- دليل البدء السريع لـ Artisan — أول عرض بالحد الأدنى.
- إعداد محرك عرض Chrome — تجهيز الملف التنفيذي، وقرار استخدام الحاوية المعزولة، ومسبار الصحة.
- أمان وعمليات Artisan — نموذج عزل الشبكة، وحدود الحاوية المعزولة، وأنماط الفشل.