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

احتوِ تطبيق ⁨NextPDF⁩ في حاوية

تريد صورة ⁨Docker⁩ صغيرة قابلة لإعادة الإنتاج تشغّل محرّك نواة ⁨NextPDF⁩ الأصلي العامل داخل العملية — ‏composer require nextpdf/core، تولّد ملفات ⁨PDF⁩ داخل عملية ⁨PHP⁩ لديك. تبني هذه الصفحة ذلك بالضبط: صورة php:8.4 بالامتدادات التي يحتاجها المحرّك فعلًا فقط، بلا تبعيات تطوير في الطبقة النهائية، وخطوط مجمَّعة، ومستخدم تشغيل غير جذر، و⁨opcache⁩ مضبوط للإنتاج، وخطوة تحقّق تُفشِل البناء إن نقص أي شيء.

هذه الصفحة فقط للمحرّك الأصلي. فجسر ⁨Chrome⁩ ‏(writeHtmlChrome عبر nextpdf/artisan) وخادم ⁨Connect⁩ بيئتا تشغيل منفصلتان لهما صورهما الأثقل الخاصة — تثبيت ⁨Chromium⁩ بلا واجهة للجسر، وخدمة طويلة العمر لـ⁨Connect.⁩ لا تضف متصفّحًا أو خادمًا إلى هذه الصورة؛ فالمحرّك الأصلي لا يحتاج أيًّا منهما.

قبل أن تبدأ، أكّد أن هذه العناصر في مكانها:

  • لتطبيقك ملفّا composer.json وcomposer.lock مُودَعان، مع nextpdf/core بوصفه تبعية.
  • لديك ملفات الخطوط التي تنوي تضمينها، وأنت مرخّص لتضمينها.
  • يمكنك تشغيل docker build على دليل تطبيقك.

هذا دليل عمليّ تشغيليّ. لا شيفرة ⁨PHP⁩ هنا تقريبًا؛ والعمل في ملف ⁨Dockerfile⁩ وبضع إعدادات بيئة.

يجب أن تفي الصورة بقيود منصّة المحرّك الحقيقية، لا أكثر. وبقراءتها مباشرةً من الحزمة، يتطلّب nextpdf/corephp: >=8.4 <9.0 وامتدادات ⁨PHP⁩ هذه:

الامتدادلماذا يحتاجه المحرّك
ext-mbstringمعالجة سلاسل متعدّدة البايتات للنصّ والترميزات
ext-intlدعم ⁨Unicode⁩ والمحليّة والتدويل
ext-gdفكّ ترميز الصور النقطية ومعالجتها
ext-opensslالتعمية للتوقيع والتجزئة الآمنة
ext-zlibضغط المجرى ‏(⁨Flate⁩) لكائنات ⁨PDF⁩
ext-curlعميل ⁨HTTP⁩ للنداءات الصادرة للمحرّك

طابِق تلك بصورة php:8.4 الرسمية. فـopenssl وcurl وzlib مُجمَّعة أصلًا في صورة ⁨PHP⁩ الرسمية، فلا تُجري لها docker-php-ext-install. أما mbstring وgd وintl فـليست مجمَّعة ويجب تثبيتها، ويحتاج كلٌّ منها ترويسات تطوير نظامه حاضرةً أولًا — ويحتاج mbstring إضافةً إلى تبعية بناء libonig-dev ‏(⁨Oniguruma⁩). لا تُضِف امتدادات محرّك لا تُدرجها الحزمة — فكل docker-php-ext-install زائد وقت بناء وسطح هجوم لا تحتاجه. والامتداد غير المتعلّق بالمحرّك الوحيد الذي تثبّته هذه الصورة هو opcache: فهو امتداد أداء وقت تشغيل، غير مجمَّع مُمكَّنًا في الصورة الرسمية، ويعتمد ضبط ⁨opcache⁩ أدناه على وجوده (انظر “⁨opcache⁩ للإنتاج”).

هذا بناء ثنائيّ المراحل. تثبّت المرحلة الأولى تبعيات ⁨Composer⁩ باستثناء حزم التطوير؛ والمرحلة الثانية هي صورة وقت التشغيل النحيلة التي تُشحن.

أضف أولًا ملف .dockerignore بجوار ملف ⁨Dockerfile.⁩ ومهمّته الأساسية إبقاء بيئة المضيف — ‏vendor/ مبنيّ على المضيف، وملفات أسرار محليّة، وخبائث بناء — خارج سياق البناء كليًّا، فيشحن COPY . /var/www/app ما تقصده فقط: بناءات أصغر وأسرع وأأمن لا تستطيع تسريب أسرار .env المحليّة أو حمل ميغابايتات من vendor/ المضيف إلى الصورة.

ويهمّ استثناء vendor/ أيضًا لأن COPY دليلٍ دمج، لا استبدال. يشغّل ملف ⁨Dockerfile⁩ أدناه RUN rm -rf /var/www/app/vendor قبل COPY --from=vendor ... /var/www/app/vendor، فلا يستطيع vendor/ مضيف في هذه الصورة النجاة أبدًا تحت شجرة التبعيات النظيفة. لكن لو أزلت يومًا حارسة rm -rf تلك، فإن vendor/ مبنيًّا على المضيف في السياق سيهبط أولًا ولن يكتب نسخ مرحلة ⁨vendor⁩ إلا المسارات التي تحتويها الشجرة النظيفة — فأي ملفات مضيف إضافية (حزمة قديمة أو مثبّتة للتطوير، صنف يتيم) ستنجو تحتها. وإبقاء vendor/ خارج السياق يسدّ تلك الثغرة بصرف النظر عن rm -rf.

# .dockerignore — keep the host environment out of the build context.
vendor/
.git/
.env
.env.local
.env.*.local
var/cache/
storage/
node_modules/
*.log

استثنِ ملفات الأسرار المحليّة الحقيقية (.env و.env.local و.env.*.local)، لا .env.* شاملًا — فذلك المحرف البديل يسقط أيضًا قوالب غير سرّية مثل .env.example التي تريد شحنها فتحمل الصورة خطّ أساس تهيئة موثَّقًا. أبقِ أي قالب بيئة مُودَع غير سرّيّ في السياق؛ واستثنِ فقط الملفات التي تحمل أسرارًا محليّة فعلًا.

# syntax=docker/dockerfile:1
# ---- Stage 1: dependencies (no dev) ---------------------------------------
FROM composer:2 AS vendor
WORKDIR /app
COPY composer.json composer.lock ./
# Install production dependencies only. --no-dev excludes phpunit, phpstan,
# infection, and the other require-dev tooling from the shipped image.
# --optimize-autoloader builds a class map for the *vendor* tree here; the
# application's own classes are not present in this stage yet, so they are
# optimized after the source copy in the runtime stage (see below).
RUN composer install \
--no-dev \
--no-interaction \
--no-progress \
--prefer-dist \
--optimize-autoloader \
--no-scripts
# ---- Stage 2: runtime ------------------------------------------------------
FROM php:8.4-cli AS runtime
# System headers for the gd, intl, and mbstring extensions that need compiling.
# The PHP image already provides openssl, curl, and zlib, so those are NOT
# listed; gd, intl, and mbstring are installed below. opcache has no system
# headers and is installed in the same step. mbstring is built against
# Oniguruma, so libonig-dev is in the *-dev set and its runtime lib (libonig5)
# is preserved by the same detection below.
#
# Build the *-dev headers (which pull in the runtime libs), compile the
# extensions, then mark only the runtime shared libraries the extensions
# actually link against so they survive the --auto-remove purge of the headers.
# Removing libicu / libpng / libjpeg / libfreetype / libonig here would unlink
# intl.so, gd.so, or mbstring.so at runtime ("undefined symbol" / "cannot open
# shared object file").
RUN set -eux; \
savedAptMark="$(apt-mark showmanual)"; \
apt-get update; \
apt-get install -y --no-install-recommends \
libicu-dev \
libpng-dev \
libjpeg62-turbo-dev \
libfreetype6-dev \
libonig-dev; \
docker-php-ext-configure gd --with-freetype --with-jpeg; \
docker-php-ext-install -j"$(nproc)" gd intl mbstring opcache; \
# Detect the runtime .so dependencies of the just-built extensions and
# mark them manual so --auto-remove keeps them while dropping the headers.
apt-mark auto '.*' > /dev/null; \
apt-mark manual $savedAptMark > /dev/null; \
find /usr/local/lib/php/extensions -type f -name '*.so' -exec \
sh -c 'ldd "$1" 2>/dev/null \
| awk "/=>/ { print \$3 }" \
| grep -E "^/" \
| xargs -r dpkg-query -S 2>/dev/null \
| cut -d: -f1 \
| sort -u \
| xargs -r apt-mark manual' _ {} \; ; \
apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false; \
rm -rf /var/lib/apt/lists/*
# Production opcache settings (see the opcache section below). The opcache
# extension is installed above (docker-php-ext-install opcache); this file only
# tunes it.
COPY docker/opcache.ini /usr/local/etc/php/conf.d/opcache.ini
# A static Composer binary for the one optimized-autoloader rebuild below. It is
# copied into the build but the final stage runs no Composer at request time.
COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
WORKDIR /var/www/app
# Application code, then the vendor tree from the dependency stage. The .dockerignore
# should already keep a host vendor/ out of the context; the rm here is a second line
# of defense so a stale host-built vendor/ can never merge under the clean one (a
# directory COPY merges, it does not replace).
COPY . /var/www/app
RUN rm -rf /var/www/app/vendor
COPY --from=vendor /app/vendor /var/www/app/vendor
# Now that the application source is present, regenerate the optimized class map
# so the APP's own classes are in the optimized autoloader, not just the vendor
# packages. --no-dev keeps require-dev out; --no-scripts avoids running
# application hooks during the image build.
RUN composer dump-autoload \
--optimize \
--no-dev \
--no-interaction \
--no-scripts \
&& rm -f /usr/bin/composer
# The bundled fonts live at /var/www/app/resources/fonts. The native engine does
# NOT read any font-path environment variable — the entrypoint registers that
# directory in PHP (see "Bundle fonts into the image" below). There is no ENV
# line for fonts here.
# Run as a non-root user (see the non-root section below).
RUN useradd --system --no-create-home --uid 10001 appuser \
&& chown -R appuser:appuser /var/www/app
USER appuser
CMD ["php", "bin/generate.php"]

تشغّل مرحلة التبعيات مع --no-scripts فلا يعمل أي خطّاف ما بعد التثبيت للتطبيق على شجرة ناقصة؛ شغّل أي خطوة بناء تطبيق (تصريف الأصول، تسخين الخبيئة) في مرحلة لاحقة بعد نسخ الشيفرة.

تثبيت ⁨Composer⁩ متعدّد المراحل (بلا تبعيات تطوير)

قسم بعنوان «تثبيت ⁨Composer⁩ متعدّد المراحل (بلا تبعيات تطوير)»

يجب ألا تحتوي الصورة المشحونة أدوات تطوير. وعَلَم --no-dev في composer install هو السطر الحامل للحمل: فهو يتخطّى كل ما تحت require-dev في nextpdf/core وتطبيقك — مُشغِّل الاختبارات والمحلّل الساكن وأدوات الطفرة — ولا مكان لأيٍّ منها في الإنتاج. اقرنه بـ--optimize-autoloader فيكون المُحمِّل التلقائيّ خريطة أصناف مولَّدة بدلًا من مسح نظام ملفات في كل طلب.

انسخ composer.json وcomposer.lock قبل بقية الشيفرة فيخبّئ ⁨Docker⁩ طبقة التبعيات ولا يعيد الحلّ إلا حين يتغيّر ملف القفل. ولأن ذلك التثبيت الأول يعمل على ملف القفل وحده — دون شيفرة التطبيق — فإن --optimize-autoloader هناك يبني خريطة أصناف لشجرة ⁨vendor⁩ فقط؛ فأصناف تطبيقك الخاصة غير موجودة بعد. ولهذا تشغّل مرحلة وقت التشغيل composer dump-autoload --optimize --no-dev --no-scripts مرة بعد نسخ الشيفرة: فهو يطوي أصناف التطبيق في خريطة الأصناف المُحسَّنة نفسها. لا تشغّل composer dump-autoload منفصلًا في شجرة عمل تطوّر فيها أيضًا (سيُودِع خريطة أصناف إنتاجية في شجرة تطوير)؛ إعادة البناء مكانها في الصورة، بعد نسخ الشيفرة، كما هو موضّح أعلاه.

يحلّ المحرّك الأصلي الخطوط من ملفات خطوط يستطيع قراءتها، لا من خطوط نظام التشغيل المثبّتة. لا يفعل تثبيت حزم fonts-* أو تشغيل fc-cache شيئًا يراه المسار الأصلي، فهذه الصورة لا تثبّت خطوط نظام. جمّع ملفات .ttf / .otf لديك تحت resources/fonts/؛ ويحملها COPY . /var/www/app أعلاه إلى الصورة أصلًا.

إيصال الملفات إلى الصورة نصف العمل فقط. فالمحرّك الأصلي المجرّد لا يقرأ أي متغيّر بيئة لبحث الخطوط — ‏NEXTPDF_FONTS_PATH هو القيمة الافتراضية لمفتاح تهيئة fonts_path في حزمة nextpdf/laravel ‏(env('NEXTPDF_FONTS_PATH', resource_path('fonts'))) ويستهلكه ذلك التكامل الإطاريّ فقط، لا nextpdf/core. فنقطة دخول php bin/generate.php عادية مع ضبط ذلك المتغيّر وحده لا تسجّل خطوطًا وتعرض التوفو نفسه الذي وُجدت هذه الصورة لمنعه. يجب أن تسجّل نقطة الدخول الدليل المجمَّع في ⁨PHP:⁩

use NextPDF\Typography\FontRegistry;
use NextPDF\Core\DocumentFactory;
use NextPDF\Graphics\ImageRegistry;
// Register the directory the Dockerfile bundled the fonts into.
$registry = new FontRegistry('/var/www/app/resources/fonts');
// (equivalently, $registry->addFontDirectory('/var/www/app/resources/fonts');)
$factory = new DocumentFactory($registry, new ImageRegistry(maxCacheBytes: 0));
$doc = $factory->create();

ذلك كامل شأن ⁨Docker⁩ بالخطوط. أما قواعد تسمية الملفات وواجهة السجلّ ونمط التسخين والقفل ومعالجة نظام الملفات للقراءة فقط فتقيم كلها في الصفحة المخصَّصة — لا تكرّرها هنا. اقرأ وفّر الخطوط للمحرّك الأصلي في الإنتاج للنمط الكامل، وسجّل الدليل نفسه الذي جمّعته.

تشغّل صور ⁨PHP⁩ الرسمية بصفتها root افتراضيًا. ومولّد ⁨PDF⁩ لا يحتاج الجذر، فأنشئ مستخدمًا غير ممتاز وانتقل إليه. يضيف ملف ⁨Dockerfile⁩ أعلاه مستخدم نظام appuser بـ⁨UID⁩ عالٍ ثابت ‏(10001)، ويمنحه ملكية شجرة التطبيق، وينتهي بـUSER appuser فتكون كل عملية تبدؤها الحاوية غير ممتازة.

أبقِ التطبيق للقراءة فقط في وقت التشغيل حيثما استطعت. يقرأ المحرّك ملفات خطوطه ويكتب خرجه وخبيئة خطوط مُحلّلة اختيارية فقط، فحاوية readOnlyRootFilesystem تعمل ما دام مسار الخرج وأي دليل خبيئة مَركبَين قابلين للكتابة. اقرن هذا بقدرات ⁨Linux⁩ مُسقَطة وعَلَم no-new-privileges في منسّقك دفاعًا في العمق.

يؤتي ⁨opcache⁩ ثماره لـعمّال ⁨PHP⁩ طويلي العمر — تجمّع ⁨FPM⁩ أو عملية ⁨Apache⁩ ‏mod_php تخدم طلبات كثيرة من عملية واحدة دافئة. تصرّف تلك العمليات أصنافك مرة ثم لا تستعلم عن ملفات المصدر في مسار ساخن أبدًا، وهو بالضبط ما يشتريه لك opcache.validate_timestamps=0. و⁨opcache⁩ ليس ممكَّنًا جاهزًا في صورة php:8.4 الرسمية، فملف ⁨Dockerfile⁩ أعلاه يثبّته بـdocker-php-ext-install opcache (يمكنك مكافئًا docker-php-ext-enable opcache إن كان الامتداد مُجمَّعًا أصلًا). وملف conf.d أدناه ضبط، لا خطوة التمكين — فهو لا يفعل شيئًا حتى يُحمَّل الامتداد. اشحنه بوصفه تضمين conf.d ‏(docker/opcache.ini، منسوخًا في ملف ⁨Dockerfile⁩):

opcache.enable=1
opcache.enable_cli=0
opcache.memory_consumption=192
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=20000
opcache.validate_timestamps=0

يعني opcache.validate_timestamps=0 أن الخبيئة لا تعيد فحص ملفات المصدر أبدًا — وهو صحيح لصورة غير قابلة للتغيير، إذ إن السبيل الوحيد لتغيّر الشيفرة صورة جديدة. اضبط memory_consumption وmax_accelerated_files على عدد أصناف تطبيقك.

CMD المعروض مولّد ⁨CLI⁩ لمرة واحدة، وopcache.enable_cli=0 صحيح له. فعملية php bin/generate.php قصيرة العمر تبدأ وتصرّف وتعرض مرة وتخرج، فخبيئة أكواد تشغيل لا تستطيع مشاركتها مع طلب تالٍ لا تعطي فائدة — اترك ⁨opcache⁩ في ⁨CLI⁩ مطفأً ولا تدفع شيئًا من كلفة ذاكرته. ولا يكسب ⁨opcache⁩ قوته إلا حيث تُعاد العملية: ‏⁨SAPI⁩ من ⁨FPM⁩/⁨Apache،⁩ أو عامل ⁨CLI⁩ طويل العمر حقًّا (مستهلك طوابير أو خادم بنمط ⁨RoadRunner⁩). ولن يضبط opcache.enable_cli=1 إلا ذلك النوع من عمّال ⁨CLI⁩ المقيمين؛ أما لمولّد لمرة واحدة هنا فأبقِه 0.

إذا شغّلت إعدادًا يستخدم التحميل المسبق لـ⁨opcache⁩ (عامل ⁨FPM⁩ طويل العمر بسكربت opcache.preload)، فاضبط opcache.preload=/path/to/preload.php وأضف opcache.preload_user=appuser فيعمل التحميل المسبق بصفته المستخدم غير الممتاز. ودون سكربت opcache.preload فعليّ، لا يفعل opcache.preload_user شيئًا، ولهذا ليس في تهيئة خطّ الأساس أعلاه — لا تضفه ما لم تضبط opcache.preload أيضًا.

أضف خطوة تحقّق فتفشل صورة مبنيّة خطأً بصوت عالٍ بدلًا من إنتاج توفو أو خطأ مميت عند أول طلب. يشحن ⁨NextPDF⁩ ⁨CLI⁩ يفحص أمره doctor بيئة ⁨PHP⁩ العاملة ويبلّغ عن الامتدادات التي يبالي بها المحرّك بالضبط — ‏openssl وzlib وmbstring وgd وcurl وintl. وتُعلن الحزمة "bin": ["bin/nextpdf"]، فيثبّت ⁨Composer⁩ في تطبيق مستهلك الملف التنفيذيّ عند vendor/bin/nextpdf (لا bin/nextpdf، وهو المسار داخل حزمة nextpdf/core نفسها). شغّله داخل الصورة المبنيّة:

Terminal window
docker run --rm your-app:latest php vendor/bin/nextpdf doctor

تؤكد نتيجة سليمة ⁨PHP 8.4⁩ وتحميل كل امتداد مطلوب. اربط الاستدعاء نفسه في البناء (أو في وظيفة دخان ⁨CI⁩) فيوقف امتداد مفقود خط الأنابيب:

Terminal window
# Fail the pipeline if the engine's environment is not healthy.
docker run --rm your-app:latest php vendor/bin/nextpdf doctor || exit 1

لفحص من طرف إلى طرف، اعرض صفحة واحدة عبر نقطة دخولك الخاصة وأكّد على الخرج، كما تصف صفحة الخطوط لفحص دخان خط.

  • php:8.4-fpm أو -apache بدلًا من -cli. استخدم ⁨SAPI⁩ الذي يخدم تطبيقك تحته فعلًا. قائمة الامتدادات متطابقة؛ ويختلف وسم الأساس وCMD/نقطة الدخول فقط. ولعامل طوابير أو وظيفة دفعة ⁨CLI،⁩ ‏-cli صحيح.
  • ‏⁨Alpine⁩ ‏(php:8.4-alpine) يحتاج أسماء حزم مختلفة. أسطر apt-get أعلاه لصورة ⁨Debian⁩ الافتراضية. على ⁨Alpine،⁩ ثبّت ترويسات *-dev بوصفها مجموعة بناء افتراضية ‏(apk add --no-cache --virtual .build-deps icu-dev libpng-dev freetype-dev libjpeg-turbo-dev oniguruma-dev) وبعد خطوة docker-php-ext-install gd intl mbstring opcache، ‏apk del .build-deps — لكن أولًا apk add --no-cache للمكتبات وقت التشغيل التي ترتبط بها الامتدادات ‏(icu-libs وlibpng وfreetype و libjpeg-turbo وoniguruma) حتى لا يفكّ حذف مجموعة البناء ربط intl.so / gd.so / mbstring.so. هذه قاعدة إبقاء مكتبات وقت التشغيل نفسها التي تفرضها كتلة ⁨Debian⁩ بـ apt-mark.
  • لا تثبّت حزم fonts-*. فهي غير مرئية للمحرّك الأصلي. جمّع ملفات الخطوط بدلًا منها — انظر صفحة الخطوط المرتبطة أعلاه.
  • ‏⁨Premium⁩ و⁨ionCube⁩ شأن صورة مختلف. تحتاج بنى ⁨NextPDF Pro⁩ / ⁨Enterprise⁩ المُرمَّزة بـ⁨ionCube⁩ مُحمِّل ⁨ionCube⁩ مثبّتًا في الصورة ومطابقًا لبناء ⁨PHP⁩ الدقيق للحاوية ‏(⁨8.4⁩، ‏⁨NTS⁩ مقابل ⁨ZTS⁩). وذلك خارج نطاق صورة نواة؛ إن نشرت ⁨premium،⁩ فاتبع قسم ⁨Docker⁩ في إعداد مُحمِّل ⁨ionCube⁩.
  • أبقِ vendor/ مضيف خارج سياق البناء. يُبقي .dockerignore (مستثنيًا vendor/ و .git/ والخبائث المحليّة) شجرة المضيف خارج السياق كليًّا — وهو ما يجعل البناء صغيرًا وسريعًا وخاليًا من الأسرار المحليّة المسرَّبة. وهو يحرس أيضًا حالة دمج الدليل: ‏COPY دليلٍ دمج، لا استبدال، فـvendor/ مبنيًّا على المضيف بلغ السياق سيهبط أولًا ولن يكتب COPY --from=vendor /app/vendor /var/www/app/vendor إلا المسارات التي تحتويها شجرة التبعيات النظيفة. وفي ملف ⁨Dockerfile⁩ هذا يزيل RUN rm -rf /var/www/app/vendor قبل نسخ ⁨vendor⁩ أي دليل كهذا أصلًا، فلا يمكن أن تحدث تلك البقايا هنا؛ ولا يعود خطر الدمج إلا إذا أسقطت حارسة rm -rf تلك، ولهذا فإن استثناء .dockerignore هو الإصلاح الدائم.
  • لا تشحن تبعيات تطوير. يُبقي --no-dev أدوات الاختبار والتحليل وحزمها المنقولة خارج صورة وقت التشغيل وسطح هجومها.
  • شغّل غير ممتاز. يضمن USER appuser النهائيّ ألا تشغّل أي عملية حاوية بصفتها جذرًا. اقرنه بنظام ملفات جذر للقراءة فقط وقدرات مُسقَطة في منسّقك.
  • ثبّت صورة الأساس. ثبّت php:8.4 بمُلخَّص في الإنتاج فلا يستطيع بناء جديد سحب أساس متغيّر بصمت، وأعد البناء على وتيرة لالتقاط رُقع الأمان عمدًا.
  • أبقِ الخطوط والتراخيص خارج الطبقات العامة. جمّع فقط خطوطًا أنت مرخّص لتضمينها، ولا تطهُ ملف ترخيص ⁨premium⁩ في صورة تُدفع علنًا — بل مَركِبه في وقت التشغيل.

لا يدّعي هذا الدليل أي ادعاء معياري. وتُقرأ حقائق المنصّة مباشرةً من حزمة nextpdf/core: قيد php: >=8.4 <9.0 والامتدادات المطلوبة ext-mbstring وext-intl وext-gd و ext-openssl وext-zlib وext-curl. وأمر التحقّق هو معالج doctor لـ⁨CLI⁩ ‏nextpdf الحقيقيّ — المُعلَن بوصفه "bin": ["bin/nextpdf"] في nextpdf/core ولذا المثبَّت عند vendor/bin/nextpdf في تطبيق مستهلك — الذي يبلّغ عن مجموعة الامتدادات نفسها. ويسجّل المحرّك الأصلي الخطوط عبر NextPDF\Typography\FontRegistry (وسيط مُنشئ الدليل / addFontDirectory()) مربوطًا عبر NextPDF\Core\DocumentFactory؛ وNEXTPDF_FONTS_PATH هو مفتاح تهيئة fonts_path لحزمة nextpdf/laravel ‏(⁨env('NEXTPDF_FONTS_PATH', resource_path('fonts'))⁩)، لا متغيّر يقرأه nextpdf/core. وسلوك السجلّ موثّق في صفحة الخطوط المرتبطة تحت “انظر أيضًا”.