إرسال ملف PDF مُولَّد بريديًّا بوصفه مرفق مُرسِل
لمحة سريعة
قسم بعنوان «لمحة سريعة»إرسال فاتورةٍ أو إيصالٍ أو تقريرٍ بريديًّا من أكثر ما تفعله بملف PDF مولَّد شيوعًا. والطريقة النظيفة لذلك أن تبني المستند، وتأخذ بايتاته الخام، وتسلّم تلك البايتات مباشرةً إلى واجهة المرفقات لمُرسِلك. ولا تحتاج إلى ملفٍّ مؤقّتٍ على القرص في أيّ لحظة.
هذا دليلٌ عمليّ تشغيلي. يفترض أنّك تعرف بالفعل كيفية إرسال البريد في إطارك. وجانب
NextPDF استدعاءٌ واحد: يُرجِع Document::getPdfData() بايتات صيغة المستند المحمول
(PDF) الخام سلسلةً. وجانب المرفق يخصّ مُرسِلك بالكامل — يستخدم هذا الدليل
Attachment::fromData() في Laravel وEmail::attach() في Symfony Mailer.
NextPDF لا يشحن مساعد بريد. فلا تابعَ “أرسل هذا الـPDF بريديًّا” على مستند، وعليك أن تتشكّك في أيّ مثالٍ يُظهر واحدًا. وواجهة المرفقات دائمًا واجهة إطارك.
هذه الصفحة هي النظير الصادر لـ تضمين الملفات داخل PDF. فذلك الدليل يُرفِق الملفات داخل ملف PDF بوصفها مجاري مضمَّنة؛ وهذا الدليل يُرفِق ملف PDF المنتهي ببريد. وهما عمليّتان مختلفتان — لا تخلط بينهما.
احصل على بايتات PDF الخام
قسم بعنوان «احصل على بايتات PDF الخام»مهما فعلت غير ذلك، خطوة NextPDF واحدة: أنتِج البايتات.
<?php
declare(strict_types=1);
use NextPDF\Core\Document;
// Standalone entrypoint: the static factory wires the default dependencies.// (The bare `new Document(...)` constructor requires injected collaborators.)$document = Document::createStandalone();$document->addPage();$document->cell(0, 10, 'Invoice #1042', newLine: true);
// Raw PDF bytes, built in memory. No file is written.$bytes = $document->getPdfData();يبني getPdfData() المستند ويُرجِع بايتاته سلسلةً. ولا يكتب شيئًا إلى القرص ولا
يرسل أيّ ترويسات بروتوكول نقل النصّ التشعّبي (HTTP)، وهو بالضبط ما تريده لمرفق.
إن كنت تحمل المستند فقط عبر نوع NextPDF\Contracts\PdfDocumentInterface (مثلًا،
قيمةٌ سلّمها لك تكامل الإطار)، فاستخدم المكافئ على مستوى العقد بدلًا من ذلك:
use NextPDF\Contracts\OutputDestination;
$bytes = $document->output(dest: OutputDestination::String);output(dest: OutputDestination::String) مُصرَّحٌ به على
PdfDocumentInterface ويُرجِع البايتات الخام نفسها دون إصدار أيّ ترويسات. استخدم
getPdfData() حين تحمل NextPDF\Core\Document محدّدًا، وصيغة output(...) حين لا
تملك إلّا الواجهة.
إن استخدمت تكامل Laravel أو Symfony، فاحلّ مستندًا جديدًا من الحاوية بدلًا من إنشاء واحدٍ مباشرةً — انظر أرجِع ملف PDF مولَّدًا من متحكّم لمسار الحلّ في كلّ إطار. وكلّ ما أدناه يعمل بالطريقة نفسها بصرف النظر عن كيفية حصولك على المستند.
Laravel: أرفِق بايتات الذاكرة إلى Mailable
قسم بعنوان «Laravel: أرفِق بايتات الذاكرة إلى Mailable»يأخذ Attachment::fromData() في Laravel ردّ نداءٍ يُرجِع البايتات الخام، زائد
اسم ملفّ. ولا ملفَّ مؤقّت. نفّذ attachments() على Mailable لديك وأرجِع
Attachment واحدًا.
<?php
declare(strict_types=1);
namespace App\Mail;
use App\Models\Invoice;use Illuminate\Mail\Mailable;use Illuminate\Mail\Mailables\Attachment;use Illuminate\Mail\Mailables\Content;use Illuminate\Mail\Mailables\Envelope;use NextPDF\Core\Document;
final class InvoiceMail extends Mailable{ public function __construct(private readonly Invoice $invoice) {}
public function envelope(): Envelope { return new Envelope(subject: "Invoice #{$this->invoice->number}"); }
public function content(): Content { return new Content(markdown: 'mail.invoice'); }
/** @return array<int, Attachment> */ public function attachments(): array { return [ Attachment::fromData(fn (): string => $this->buildPdf(), "invoice-{$this->invoice->number}.pdf") ->withMime('application/pdf'), ]; }
private function buildPdf(): string { // Standalone document: the static factory wires the default // dependencies, so this example is self-contained. If you use the // nextpdf/laravel integration, resolve a document via its documented // binding instead — see the integration page linked below. $document = Document::createStandalone(); $document->addPage(); $document->cell(0, 10, "Invoice #{$this->invoice->number}", newLine: true);
return $document->getPdfData(); }}أرسله كالمعتاد:
use App\Mail\InvoiceMail;use Illuminate\Support\Facades\Mail;
Mail::to($invoice->customerEmail)->send(new InvoiceMail($invoice));يُستدعى ردّ نداء fromData() كسولًا عند بناء الرسالة، فيُولَّد ملف PDF وقت الإرسال،
لا عند الإنشاء. اضبط ->withMime('application/pdf') كي يعامل عميل المتلقّي الجزء
بوصفه PDF بدلًا من التخمين من الامتداد. وداخل متحكّم يمكنك بدلًا من ذلك استدعاء
$message->attachData($bytes, $name, ['mime' => 'application/pdf']) على رسالةٍ
خام، لكنّ Attachment::fromData() على Mailable هو الصيغة الحديثة الاصطلاحية.
Symfony Mailer: أرفِق البايتات إلى Email
قسم بعنوان «Symfony Mailer: أرفِق البايتات إلى Email»يقبل Email::attach() في Symfony Mailer الجسم سلسلةً في الذاكرة، مع اسم ملفّ
ونوع محتوًى صريحَين. ومجدّدًا، لا ملفَّ مؤقّت.
<?php
declare(strict_types=1);
namespace App\Mailer;
use NextPDF\Core\DocumentFactory;use Symfony\Component\Mailer\MailerInterface;use Symfony\Component\Mime\Email;
final class InvoiceMailer{ public function __construct( private readonly MailerInterface $mailer, private readonly DocumentFactory $documents, ) {}
public function sendInvoice(string $to, int $invoiceId): void { // Build a fresh document from the factory, which wires the default // dependencies for you. (Use Document::createStandalone() if you do // not have the factory injected.) $document = $this->documents->create(); $document->addPage(); $document->cell(0, 10, "Invoice #{$invoiceId}", newLine: true);
$email = (new Email()) ->from('billing@example.com') ->to($to) ->subject("Invoice #{$invoiceId}") ->text('Your invoice is attached.') ->attach( $document->getPdfData(), "invoice-{$invoiceId}.pdf", 'application/pdf', );
$this->mailer->send($email); }}يأخذ attach(string $body, ?string $name, ?string $contentType) البايتات مباشرةً.
مرّر 'application/pdf' بوصفه الوسيطة الثالثة كي يُوسَم الجزء بنوعه الصحيح. وإن
فضّلت الإرفاق من مجرى، فـattachFromPath() موجود، لكن للمحتوى المولَّد تتجنّب صيغة
attach() في الذاكرة ذهابًا وإيابًا لا لزوم له إلى القرص.
افعلها من مهمّةٍ مُدرَجة في طابور
قسم بعنوان «افعلها من مهمّةٍ مُدرَجة في طابور»بناء ملف PDF متعدّد الصفحات وإرسال البريد كلاهما بطيءٌ بما يكفي لئلّا تفعله على خيط الطلب. أدرِج العمل في طابور. والنمط أن تُرسِل مهمّةً (أو، في Laravel، تُدرِج Mailable نفسه) وتبني ملف PDF على العامل.
أبسط صيغة في Laravel: اجعل Mailable من نوع ShouldQueue. ولأنّ ردّ نداء
Attachment::fromData() يعمل عند بناء الرسالة المُدرَجة، يُولَّد ملف PDF على
العامل، لا عند الإرسال.
use Illuminate\Contracts\Queue\ShouldQueue;use Illuminate\Mail\Mailable;
final class InvoiceMail extends Mailable implements ShouldQueue{ // ... same envelope(), content(), attachments() as above ...}// Dispatched to the queue; the worker builds the PDF and sends the mail.Mail::to($invoice->customerEmail)->queue(new InvoiceMail($invoice));ولـSymfony، أرسِل رسالة Messenger تحمل المعرّفات (لا البايتات)، ودَع المُعالِج
يبني ملف PDF ويرسل البريد على العامل. مرّر معرّف فاتورة، وابحث عن السجلّ في
المُعالِج، واستدعِ InvoiceMailer المبيَّن أعلاه. ومُرسِل Symfony لا تزامنيٌّ أصلًا
حين يُضبَط نقل Messenger لـSendEmailMessage، فحتى send() المُستدعى تزامنيًّا
يمكن نقله إلى عامل.
إن ولّدت ملف PDF في مهمّة توليدٍ مخصّصة ثمّ أرسلته بريديًّا، فانظر
ولّد ملف PDF في مهمّةٍ مُدرَجة في طابور
لسطح GeneratePdfJob / GeneratePdfMessage الخاصّ بالتكامل وقواعد سلامة عامله.
وشكلٌ شائع: مهمّةٌ تولّد ملف PDF وتحفظه، ومهمّةٌ ثانية (أو ردّ نداء النجاح) تقرؤه
وترسله بريديًّا. وحين تُبقي البايتات في الذاكرة عبر مهمّةٍ واحدة، تتخطّى الملفّ
كليًّا.
ملاحظات الحجم والمضمَّن والمرفق
قسم بعنوان «ملاحظات الحجم والمضمَّن والمرفق»- مرفق، لا مضمَّن. فاتورةٌ أو تقريرٌ مولَّدٌ يكون دائمًا تقريبًا ملفًّا منفصلًا
قابلًا للتنزيل، فأرفِقه. واحجز المحتوى المضمَّن
(
Content-Disposition: inlineمع مرجعcid:) للصور التي تضمّنها في جسم HTML — فملف PDF ليس محتوى جسم. - انتبه إلى الحجم. تُرمَّز مرفقات البريد بـbase64 أثناء النقل، وهو ما يُضخّم الحمولة بنحو الثلث. ويحدّ كثيرٌ من الخوادم المتلقّية الرسالة عند نحو 10–25 ميغابايت بعد الترميز. ولتقريرٍ كبير، أرفِق بريد إشعارٍ قصيرًا برابط تنزيلٍ موقَّع بدلًا من الملفّ نفسه، واخدم ملف PDF عبر HTTP — انظر أرجِع ملف PDF مولَّدًا من متحكّم.
- ابنِ مرّةً، أرفِق مرّةً. التقط البايتات المُرجَعة مرّةً في متغيّر وأعد استخدام تلك السلسلة للرسالة. لا تستدعِ تابع الإخراج النهائي مرارًا لبريدٍ واحد.
- الذاكرة على العامل. إبقاء ملف PDF كاملًا في الذاكرة جيّدٌ للفواتير والإيصالات
النموذجية. وللمستندات الكبيرة جدًّا على عاملٍ محدود، احفظ إلى مسارٍ مؤقّت بـ
save()، وأرفِق عبر Laravelattach($path)/ SymfonyEmail::attachFromPath($path)، ثمّ احذف الملفّ — مبادلًا ذهابًا وإيابًا إلى القرص بذروة ذاكرةٍ أدنى. (لمسار الذاكرة المستخدَم في كلّ مكانٍ آخر في هذه الصفحة، الصيغة المبنية على البايتات هي LaravelAttachment::fromData()/ SymfonyEmail::attach($bytes, 'name.pdf', 'application/pdf').)
ملاحظات أمنية
قسم بعنوان «ملاحظات أمنية»- لا تُدخِل أبدًا مدخل مستخدمٍ غير مُتحقَّقٍ منه في اسم ملفّ المرفق. مرّر قيمةً تتحكّم بها (رقم فاتورةٍ ولّدته)، كي لا يمكن توجيه عميل المتلقّي باسمٍ مُصاغ.
- أرسل لكلّ عميلٍ مستنده هو فقط. ابنِ ملف PDF من سجلّات الجهة المُصادَق عليها داخل المهمّة، لا من معرّفٍ مأخوذٍ على الثقة من الطلب.
- في مسارٍ مُدرَجٍ في طابور، سجّل صنف الاستثناء ومعرّف ربطٍ عند الفشل، لا رسالة
الاستثناء ولا أثر المكدّس. لا تكتب أبدًا كتلة
catchفارغة حول البناء والإرسال.
انظر أيضًا
قسم بعنوان «انظر أيضًا»- ضمّن الملفات وأنشئ حافظات PDF — العكس: أرفِق الملفات داخل ملف PDF.
- ولّد ملف PDF في مهمّةٍ مُدرَجة في طابور — انقل التوليد خارج خيط الطلب.
- أرجِع ملف PDF مولَّدًا من متحكّم — اخدم ملف PDF عبر HTTP بدلًا من إرفاقه.
- استخدام Laravel في الإنتاج — حلّ مستندٍ من الحاوية وسلامة العامل.
- استخدام Symfony في الإنتاج — عمّال Messenger ومصنع المستندات.