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

تحويل مستندات Office إلى PDF باستخدام Gotenberg

يحوِّل جسر ⁨Gotenberg⁩ مستند ⁨Office⁩ إلى ⁨PDF.⁩ يرسل المستند إلى خدمة ⁨Gotenberg⁩ المصغَّرة عبر ⁨HTTPS⁩ ويعيد بايتات ⁨PDF.⁩ عرِّف الخدمة عبر GotenbergConfig غير قابل للتغيير، وصِل عميل ⁨PSR-18⁩ ومصانع ⁨PSR-17⁩ بـGotenbergBridge، وافحص سلامة الخدمة، ثم حوِّل ملفًا من القرص أو بايتات في الذاكرة. يغطي هذا الدليل اكتشاف التنسيق المعتمد على الامتداد، ومسبار السلامة، وعقد الإخفاق المصنَّف، والتسليم إلى المعالجة اللاحقة في ⁨NextPDF⁩.

المتطلبات الأساسية موضحة من البداية:

  • نواة ⁨NextPDF⁩ وnextpdf/gotenberg مثبَّتان.
  • خدمة ⁨Gotenberg⁩ يمكن الوصول إليها عبر ⁨HTTPS.⁩ يرفض الجسر عنوان http:// العادي قبل أن تغادر أي طلب العملية.
  • عميل ⁨PSR-18⁩ ومصنعا الطلب والدفق من ⁨PSR-17⁩ مثبَّتون. لتثبيت ⁨DNS⁩ و⁨TLS⁩، تزوِّد أيضًا مصنع استجابة من ⁨PSR-17.⁩
  • يجب أن يكون الإدخال أحد تنسيقات ⁨Office⁩ الستة المعترف بها: .docx، .xlsx، .pptx، .odt، .ods، أو .odp. يرفض الجسر أي امتداد آخر مع ValueError.

هذا دليل إرشادي عملي. للاطلاع على برنامج كامل وقابل للتشغيل، اقرأ دليل البدء السريع لـ ⁨Gotenberg.⁩

ثبِّت الجسر وعميل ⁨PSR-18⁩ ومصانع ⁨PSR-17.⁩

Terminal window
composer require nextpdf/gotenberg guzzlehttp/guzzle

شغِّل خدمة ⁨Gotenberg⁩ يمكن الوصول إليها عبر ⁨HTTPS.⁩ احصل على أي رمز حامل من مدير أسرار أو من قيمة بيئة محقونة. لا يقرأ الجسر متغيرات البيئة أبدًا ولا ينشئ عميل ⁨HTTP⁩ أبدًا؛ فأنت من يزوِّد كليهما.

يأخذ GotenbergBridge::convertFile() مسارًا على القرص. يحوِّل المسار إلى صورته المعيارية لمنع الاجتياز، ويربط امتداد الملف بتنسيق مدعوم، ويفحص الحجم واسم الملف، ويرسل طلبًا متعدد الأجزاء إلى <apiUrl>/forms/libreoffice/convert. يتبع convertString() المسار نفسه للبايتات التي تملكها بالفعل، ويستخدم اسم الملف الأصلي حتى يمكن اكتشاف الامتداد.

يعتمد اكتشاف التنسيق على الامتداد. يربط الجسر .docx، .xlsx، .pptx، .odt، .ods، و.odp بتنسيقاتها ويرفض أي شيء آخر مع ValueError قبل أي حركة مرور على الشبكة. يكشف كائن النتيجة تنسيق المصدر المكتشَف كقيمة تعداد.

يجري الجسر رحلة ⁨HTTP⁩ متزامنة واحدة ذهابًا وإيابًا، محاطة بالتحقق. لا يعيد المحاولة، ولا يضع في قائمة انتظار، ولا يخزن مؤقتًا، ولا يحدد معدل الطلبات؛ فهذه الضوابط تنتمي إلى التطبيق المحيط بالجسر. تعامَل مع كل تحويل بوصفه استدعاءً بعيدًا إلى خدمة تشغِّلها لكنك لا تتحكم فيها داخل العملية، وصمِّم لزمن استجابتها وأنماط إخفاقها.

يبلِّغ الجسر عن الإخفاقات كاستثناءات مصنَّفة، ولا يعيد أبدًا نتيجة جزئية أو غير متحقَّق منها:

  • حالة غير 200، أو Content-Type بدون application/pdf، أو متن لا يبدأ بـ%PDF يثير GotenbergConvertException. يعيد الجسر نتيجة فقط عندما تجتاز الفحوص الثلاثة جميعها.
  • إخفاق عميل ⁨PSR-18⁩، بما في ذلك إخفاق الشبكة أو انتهاء المهلة، يُغلَّف بوصفه GotenbergConvertException مع الاستثناء الأصلي بوصفه السبب.
  • إخفاقات التحقق (عنوان غير ⁨HTTPS⁩، عنوان خاص أو محجوز، إدخال مفرط الحجم، اسم ملف غير آمن) تثير RuntimeException قبل أي حركة مرور على الشبكة.
  • امتداد ملف غير معترف به يثير ValueError قبل أي حركة مرور على الشبكة.
// Configuration (final readonly):
new GotenbergConfig(
string $apiUrl, // required, must be HTTPS
int $timeout = 30, // hard transfer timeout, seconds
int $maxFileSize = 52_428_800, // 50 MiB
string $apiKey = '', // #[SensitiveParameter]; Bearer when non-empty
list<string> $pinnedPublicKeys = [], // sha256/<base64>
list<string> $backupPublicKeys = [],
)
GotenbergConfig::fromArray(array $config): self
GotenbergConfig::isValid(): bool
// The bridge:
new GotenbergBridge(
GotenbergConfig $config,
ClientInterface $httpClient, // PSR-18
RequestFactoryInterface $requestFactory, // PSR-17
StreamFactoryInterface $streamFactory, // PSR-17
?LoggerInterface $logger = null, // PSR-3
?HtmlSecurityPolicyInterface $htmlSecurityPolicy = null,
?ResponseFactoryInterface $responseFactory = null, // enables pinned transport
)
GotenbergBridge::isAvailable(): bool
GotenbergBridge::convertFile(string $path): GotenbergConvertResult
GotenbergBridge::convertString(string $bytes, string $originalFilename): GotenbergConvertResult

يكشف كائن النتيجة pdfData، وتعداد sourceFormat، وisValid() (صحيح عندما يكون المتن غير فارغ ويبدأ بـ%PDF)، وsize(). للاطلاع على المرجع الكامل للحقول، وخريطة مفاتيح fromArray()، وقواعد اختيار النقل، راجع صفحة إعدادات ⁨Gotenberg⁩ المرتبطة ضمن طالِع أيضًا.

عيِّنة شيفرة — البدء السريع

قسم بعنوان «عيِّنة شيفرة — البدء السريع»

عرِّف الخدمة، وصِل الجسر، وافحص جاهزيته، وحوِّل ملفًا واحدًا.

convert-quickstart.php
<?php
declare(strict_types=1);
require __DIR__ . '/vendor/autoload.php';
use NextPDF\Gotenberg\GotenbergBridge;
use NextPDF\Gotenberg\GotenbergConfig;
use NextPDF\Gotenberg\GotenbergConvertException;
$config = new GotenbergConfig(
apiUrl: 'https://gotenberg.example.com',
timeout: 60,
apiKey: getenv('GOTENBERG_TOKEN') ?: '',
);
$bridge = new GotenbergBridge(
config: $config,
httpClient: $httpClient, // your PSR-18 client
requestFactory: $requestFactory, // your PSR-17 factory
streamFactory: $streamFactory, // your PSR-17 factory
responseFactory: $responseFactory, // enables the pinned transport
);
// Probe before converting. The probe validates the URL with no network
// traffic, then sends a HEAD to <apiUrl>/health.
if (!$bridge->isAvailable()) {
throw new RuntimeException('Gotenberg is not reachable.');
}
try {
$result = $bridge->convertFile('/path/to/report.docx');
} catch (GotenbergConvertException $exception) {
// Bad config, HTTP failure, non-200, wrong Content-Type, or non-PDF body.
throw $exception;
}
if (!$result->isValid()) {
throw new RuntimeException('Result is not a valid PDF.');
}
file_put_contents('/path/to/report.pdf', $result->pdfData);

الفئة هي NextPDF\Gotenberg\GotenbergConfig (يستخدم السطر أعلاه مساحة الاسم الدقيقة التي يجب أن تستوردها شيفرتك). يعيد isAvailable() القيمة false ولا يثير استثناءً أبدًا لعنوان فارغ أو غير ⁨HTTPS⁩ أو عنوان خاص، أو لأي خطأ في الشبكة؛ فحالة أقل من 500 من /health تعني أنها متاحة.

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

OfficeConverter.php
<?php
declare(strict_types=1);
use NextPDF\Gotenberg\GotenbergBridge;
use NextPDF\Gotenberg\GotenbergConvertException;
use Psr\Log\LoggerInterface;
use RuntimeException;
use ValueError;
final readonly class OfficeConverter
{
public function __construct(
private GotenbergBridge $bridge,
private LoggerInterface $logger,
) {}
public function convert(string $path): string
{
try {
$result = $this->bridge->convertFile($path);
} catch (GotenbergConvertException $exception) {
// Transport, non-200, wrong Content-Type, or non-PDF body.
// Retry only on transport-level or 502/503/504 causes, with
// bounded exponential backoff and jitter — never blind retries.
$this->logger->error('gotenberg.convert.failed', [
'path' => basename($path),
'exception' => $exception::class,
]);
throw $exception;
} catch (ValueError $exception) {
// Extension is not one of the six recognized Office formats.
$this->logger->warning('gotenberg.convert.unsupported_format', [
'path' => basename($path),
]);
throw $exception;
} catch (RuntimeException $exception) {
// Non-HTTPS URL, private address, oversized input, or unsafe name.
$this->logger->error('gotenberg.convert.rejected', [
'path' => basename($path),
'exception' => $exception::class,
]);
throw $exception;
}
if (!$result->isValid()) {
throw new RuntimeException('Gotenberg returned an invalid PDF body.');
}
return $result->pdfData;
}
}

أعِد المحاولة فقط عند GotenbergConvertException على مستوى النقل (استثناء عميل ⁨PSR-18⁩ مغلَّف) وعند أخطاء الخادم التي لا تخلّف أثرًا (502، 503، 504). تعني استجابة من فئة 400 عادةً أن الإدخال خاطئ، لذا ستفشل إعادة المحاولة بالطريقة نفسها. حدِّد إجمالي المحاولات وزمن التنفيذ الفعلي الإجمالي. حدِّد عدد التحويلات الجارية بما يتناسب مع السعة التي يتحملها نشر ⁨Gotenberg⁩ لديك. الجسر نفسه عديم الحالة وآمن للاستخدام من عمَّال كثيرين، لكن للخدمة سعة تحويل محدودة.

  • اكتشاف التنسيق يكون بالامتداد. .docx أُعيدت تسميته إلى .txt يُرفض مع ValueError؛ و.txt أُعيدت تسميته إلى .docx يُرسَل إلى ⁨Gotenberg⁩ ويفشل هناك. عند قبول عمليات الرفع، اعتمد على التنسيق الحقيقي، لا على الاسم.
  • fromArray() متسامح بحكم التصميم. يستبدل القيم الافتراضية بصمت للإدخال المشوَّه. تحقَّق من مصفوفة المصدر في مسار بدء التشغيل لديك حتى يظهر العنوان المفقود مبكرًا كخطأ في الإعداد، لا كاستثناء في كل تحويل.
  • حدّ الحجم مُطبَّق داخل العملية. maxFileSize (الافتراضي 50 ⁨MiB⁩) يُفحَص قبل إرسال الطلب، حتى لا يستهلك ملف مفرط الحجم سعة الخدمة أبدًا. اخفض الحدّ ليطابق ما تحتاجه مستنداتك؛ فالحدّ الأصغر ضابط أرخص ضد حجب الخدمة.
  • المسبار ليس مجانيًا. استدعِ isAvailable() من نقطة نهاية الجاهزية أو السلامة، لا قبل كل تحويل. تشغيله مع كل تحويل يضاعف معدل طلباتك على الخدمة دون أي فائدة.
  • لا يوجد تخزين مؤقت داخل العملية. إذا جرى تحويل المستند نفسه تكرارًا، فخزِّن ⁨PDF⁩ الناتج مؤقتًا في تطبيقك، مفهرسًا بتجزئة محتوى الإدخال.
  • renderTimeMs تضبطه أنت. حقل التوقيت في النتيجة هو 0.0 ما لم يَقِسه تكاملك ويضبطه. قِس الاستدعاء بنفسك إن احتجت إلى الرقم.

طوال مدة الطلب، يحتجز التحويل اتصالًا واحدًا وعاملًا واحدًا من ⁨LibreOffice⁩ على جانب ⁨Gotenberg⁩، وتحويل ⁨Office⁩ يستغرق وقتًا. اضبط timeout بناءً على زمن استجابة التحويل المقاس لمستنداتك الحقيقية، مع هامش مناسب. اجعله أقل من مهلة أي بوابة وسيطة أعلى أو من max_execution_time الخاص بـ ⁨PHP⁩، حتى تنتهي مهلة الجسر أولًا فتحصل على استثناء مصنَّف بدلًا من عملية منتهية قسرًا. حدِّد التزامن بقائمة انتظار أو سيمافور أو مجمَّع عمَّال بحجم يناسب سعة الخدمة. لا يوجد تخزين مؤقت داخل العملية؛ أضِف واحدًا في تطبيقك إذا حوَّلت الإدخال نفسه تكرارًا.

  • ⁨HTTPS⁩ وفحص العنوان قبل الإرسال. يرفض الجسر عنوانًا غير ⁨HTTPS⁩ ووجهة تُحَل إلى مساحة عناوين خاصة أو محجوزة قبل أن يغادر أي طلب العملية. كل استدعاء يُعاد المحاولة يعيد تشغيل ذلك التحقق، لذا لا يمكن لإعادة المحاولة تجاوز ضابط ⁨SSRF.⁩
  • النقل المثبَّت عند الطلب. عندما تزوِّد مصنع استجابة وتثبيتات (أو عندما توجد مجموعة ⁨IP⁩ محلَّلة)، يربط الجسر الاتصال بالعناوين المحلَّلة، ويفرض تثبيت ⁨SPKI⁩، ويتحقق من النظير والمضيف، ويطبِّق المهلة، ويعطِّل اتباع إعادة التوجيه. جهِّز تثبيتًا احتياطيًا قبل تدوير الشهادة.
  • لا تثق بنوع المحتوى المعلَن لعملية رفع. عند قبول عمليات رفع المستخدمين، تحقَّق من نوع الملف الحقيقي بنفسك؛ فخريطة الامتداد إلى التنسيق قرار توجيه، لا فحص أصالة.
  • الأسرار محجوبة وغير قابلة للتغيير. apiKey يحمل #[SensitiveParameter]، والإعداد final readonly. احصل على الرمز من مدير أسرار؛ لا تودِعه في المستودع أبدًا. يتضمن سجل التحويل عند تدوينه العنوان واسم الملف والتنسيق وطول المحتوى — لا محتويات الملف ولا الرمز أبدًا.
  • لا تكتب أبدًا كتلة catch فارغة. كل مثال يلتقط النوع المحدَّد ويدوِّن مع السياق.

للاطلاع على نموذج الأمان والنشر الكامل، راجع صفحة أمان وعمليات ⁨Gotenberg.⁩ عقد نقل ⁨PSR-18⁩ وإرشاد عدم الوثوق بنوع المحتوى مرتبطان ببنودهما في صفحة استخدام الإنتاج الأعلى.

لا يقدِّم هذا الدليل بحد ذاته أي ادعاء معياري. سلوك نقل ⁨PSR-18⁩ للجسر (لا يثير العميل استثناءً إلا عندما يتعذَّر عليه إرسال استجابة أو تحليلها؛ و4xx/5xx قيمة إرجاع عادية)، وإرشاد التحقق من رفع الملفات، ونموذج تثبيت ⁨TLS⁩، مثبتة إلى ⁨PSR-18⁩ و⁨OWASP⁩ و⁨RFC 7469⁩ في صفحتَي استخدام الإنتاج والإعداد الأعلى لـ ⁨Gotenberg.⁩ تعيد صفحة دليل الطبخ هذه ذكر الاستخدام وتؤجِّل تلك الاستشهادات إلى تلك الصفحات. ينتج الجسر بايتات ⁨PDF⁩ ويتوقف. التوقيع، وملفات تعريف ⁨PDF/A⁩، ووضع العلامات المائية، شؤون معالجة ⁨NextPDF⁩ اللاحقة وإمكانات في الإصدار التجاري، وليست جزءًا من هذا الجسر.