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

إرسال ملف ⁨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 واحدًا.

app/Mail/InvoiceMail.php
<?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⁩ الجسم سلسلةً في الذاكرة، مع اسم ملفّ ونوع محتوًى صريحَين. ومجدّدًا، لا ملفَّ مؤقّت.

src/Mailer/InvoiceMailer.php
<?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⁩ على العامل، لا عند الإرسال.

app/Mail/InvoiceMail.php (queued)
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()، وأرفِق عبر ⁨Laravel⁩ attach($path) / ⁨Symfony⁩ Email::attachFromPath($path)، ثمّ احذف الملفّ — مبادلًا ذهابًا وإيابًا إلى القرص بذروة ذاكرةٍ أدنى. (لمسار الذاكرة المستخدَم في كلّ مكانٍ آخر في هذه الصفحة، الصيغة المبنية على البايتات هي ⁨Laravel⁩ Attachment::fromData() / ⁨Symfony⁩ Email::attach($bytes, 'name.pdf', 'application/pdf').)
  • لا تُدخِل أبدًا مدخل مستخدمٍ غير مُتحقَّقٍ منه في اسم ملفّ المرفق. مرّر قيمةً تتحكّم بها (رقم فاتورةٍ ولّدته)، كي لا يمكن توجيه عميل المتلقّي باسمٍ مُصاغ.
  • أرسل لكلّ عميلٍ مستنده هو فقط. ابنِ ملف ⁨PDF⁩ من سجلّات الجهة المُصادَق عليها داخل المهمّة، لا من معرّفٍ مأخوذٍ على الثقة من الطلب.
  • في مسارٍ مُدرَجٍ في طابور، سجّل صنف الاستثناء ومعرّف ربطٍ عند الفشل، لا رسالة الاستثناء ولا أثر المكدّس. لا تكتب أبدًا كتلة catch فارغة حول البناء والإرسال.