von der Pdf\Invoice-Klasse bis zur Batch-Verarbeitung
Wer Rechnungen als PDF individuell gestalten will, landet unweigerlich bei Pdf\AbstractPdf, Pdf\Invoice und Zend_Pdf, nicht bei der Sales-Business-Logik. Dieser Artikel zeigt, wie eine gezielte Preference statt einer vollstandigen Klassenkopie die PDF-Generierung erweitert, wie eigene Renderer per di.xml registriert werden und wie sich Rechnungen als PDF performant per CLI-Batch oder Message Queue statt synchron im Checkout erzeugen lassen.
Inhaltsverzeichnis
- 1. Warum PDF-Generierung eine eigene Architekturschicht ist
- 2. Pdf\AbstractPdf, Pdf\Invoice und Pdf\Shipment im Uberblick
- 3. Preference vs. gezielte Method-Extension
- 4. Eigene Line-Item-Renderer via di.xml virtualType
- 5. Zend_Pdf\Page und Zend_Pdf\Font fur eigene Inhalte
- 6. Logo, Kopf- und Fusszeile anpassen
- 7. Batch-Generierung per CLI-Command
- 8. Performance: synchron im Checkout vs. Queue
- 9. PDF-Generierung im direkten Vergleich
- 10. Zusammenfassung
- 11. FAQ
1. Warum PDF-Generierung eine eigene Architekturschicht ist
Sobald ein Kunde eine Rechnung oder einen Lieferschein anfordert, greift Magento nicht auf ein Template-System wie phtml oder LESS zuruck, sondern auf eine dedizierte Rendering-Schicht rund um Zend_Pdf. Diese PDF-Generierung ist von der eigentlichen Sales-Business-Logik sauber getrennt: Order\Invoice, Order\Shipment und Order\Creditmemo erzeugen nur Datensatze, die anschliessend von eigenstandigen Pdf-Klassen in ein binares PDF-Dokument ubersetzt werden. Wer Rechnungen als PDF individuell gestalten will, landet fast immer im Namespace Magento\Sales\Model\Order\Pdf, nicht im Invoice-Model selbst.
Diese Trennung hat einen Grund: Layout-Anderungen an Rechnungen als PDF, etwa eine zusatzliche Steuerzeile, ein QR-Code oder ein individuelles Kopfzeilenlayout, sollen die Geschaftslogik der Rechnungserstellung nicht beruhren. Wer beides vermischt, riskiert bei jedem Magento-Update Konflikte in Klassen, die eigentlich nur Zahlen berechnen, nicht Text auf ein PDF-Blatt zeichnen. Dieser Artikel bleibt bewusst auf der Rendering-Ebene: Es geht nicht um Retourenprozesse, nicht um den E-Mail-Versand von Belegen und nicht um die betriebswirtschaftliche Logik hinter Gutschriften, sondern ausschliesslich darum, wie das PDF-Dokument selbst erzeugt wird.
In der Praxis bedeutet das: Wer die PDF-Generierung anpasst, arbeitet mit Zend_Pdf_Page, Zend_Pdf_Font und den Renderer-Klassen unter Pdf\Items, nicht mit Observern auf sales_order_invoice_save_after. Diese Unterscheidung entscheidet, welche Erweiterungstechnik im konkreten Fall die richtige ist, und genau das behandeln die folgenden Abschnitte im Detail.
2. Pdf\AbstractPdf, Pdf\Invoice und Pdf\Shipment im Uberblick
Magento\Sales\Model\Order\Pdf\AbstractPdf bildet das Fundament der gesamten PDF-Generierung. Sie stellt geschutzte Hilfsmethoden bereit: insertLogo() zeichnet das Store-Logo als Zend_Pdf_Image auf die aktuelle Seite, insertAddresses() rendert Rechnungs- und Lieferadresse nebeneinander, insertOrder() gibt Bestellnummer, Datum und Zahlart aus. Alle drei konkreten Unterklassen, Pdf\Invoice, Pdf\Shipment und Pdf\Creditmemo, erben diese Methoden und implementieren zusatzlich die offentliche Methode getPdf(), die aus einem Array von Invoice- beziehungsweise Shipment-Objekten ein vollstandiges Zend_Pdf-Dokument zusammensetzt.
Wichtig fur jede Anpassung: AbstractPdf ist eine abstrakte Klasse mit ausschliesslich konkreten, teils protected Methoden, kein Interface. Es gibt kein PdfInvoiceInterface, das man per Service Contract implementieren konnte. Wer also am Layout der PDF-Generierung etwas andern will, muss zwangslaufig mit den konkreten Klassen arbeiten, entweder uber Vererbung oder uber Plugins auf die offentlich sichtbaren Methoden.
Innerhalb von getPdf() iteriert Pdf\Invoice uber jede Rechnungsposition und delegiert das Zeichnen jeder Zeile an eine Renderer-Klasse aus dem Namespace Pdf\Items\Invoice. Welche Renderer-Klasse fur welchen Produkttyp zustandig ist, wird nicht in getPdf() selbst entschieden, sondern uber die Konfiguration in etc/sales.xml nachgeschlagen, ein Detail, das im Abschnitt zu eigenen Renderern wichtig wird.
3. Preference vs. gezielte Method-Extension
Da AbstractPdf, Invoice und Shipment konkrete Klassen mit protected Methoden sind, greift ein Plugin hier nur begrenzt: Der Magento-Interceptor-Mechanismus kann ausschliesslich public Methoden abfangen. insertLogo(), insertAddresses() oder die interne Verwaltung der Y-Koordinate sind protected und damit fur Plugins unerreichbar. Wer insertLogo() so andern will, dass das Logo rechtsbundig statt linksbundig erscheint, kommt an einer Preference nicht vorbei.
Der entscheidende Unterschied liegt in der Grosse der Preference. Eine vollstandige Preference, die die komplette Pdf\Invoice-Klasse ersetzt und bei jedem Core-Update manuell nachgezogen werden muss, ist in den allermeisten Fallen unnotig riskant. Der pragmatischere Weg: eine dunne Unterklasse, die ausschliesslich die eine betroffene protected Methode uberschreibt und fur alles andere parent:: aufruft. Diese gezielte Method-Extension minimiert die Angriffsflache fur Merge-Konflikte, weil neue Core-Methoden, die Magento in kunftigen Versionen hinzufugt, automatisch erhalten bleiben.
Fur periphere Aufgaben, etwa das Loggen jeder generierten Rechnung als PDF oder das Auslosen eines Events nach erfolgreicher Generierung, ist ein Plugin auf die offentliche Methode getPdf() die bessere Wahl, weil getPdf() public ist. Die Faustregel: Preference fur Eingriffe in protected Rendering-Details, Plugin fur alles, was vor oder nach dem eigentlichen Rendering passiert. Genau diese Kombination, ein schmales Preference plus periphere Plugins, ist der akzeptierte Kompromiss innerhalb der Magento-Kernarchitektur fur die PDF-Generierung.
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
<!-- Narrow preference: only Pdf\Invoice is replaced, not the whole Pdf subsystem -->
<preference for="Magento\Sales\Model\Order\Pdf\Invoice" type="Mironsoft\PdfCustomizer\Model\Order\Pdf\Invoice" />
<!-- Virtual type parametrizes an existing renderer instead of duplicating it -->
<virtualType name="Mironsoft\PdfCustomizer\Model\Order\Pdf\Items\Invoice\BundleWithQr" type="Mironsoft\PdfCustomizer\Model\Order\Pdf\Items\Invoice\DefaultInvoiceWithTaxBreakdown">
<arguments>
<argument name="showQrReference" xsi:type="boolean">true</argument>
</arguments>
</virtualType>
</config>
4. Eigene Line-Item-Renderer via di.xml virtualType
Jede Position einer Rechnung als PDF wird von einer eigenen Renderer-Klasse gezeichnet, die von Pdf\Items\AbstractItems erbt. Fur einfache Produkte ist das Pdf\Items\Invoice\DefaultInvoice, fur Bundle-Produkte existiert ein eigener Renderer mit verschachtelter Darstellung. Welche Klasse fur welchen Produkttyp verwendet wird, legt etc/sales.xml pro Modul fest, uber das type-Attribut im Knoten order/pdf/items/item.
Der Clou dabei: Das block-Attribut in sales.xml muss keinen fest verdrahteten Klassennamen enthalten, sondern kann auf einen virtualType aus di.xml zeigen. Damit lasst sich eine bestehende Renderer-Klasse mit zusatzlichen Konstruktor-Argumenten parametrisieren, ohne eine komplett neue Klasse schreiben zu mussen, etwa um pro Produkttyp eine Steuerzeile oder eine Zusatzgebuhr auf der Rechnung als PDF einzublenden.
In der eigenen Renderer-Klasse selbst greift man uber $this->getPdf() auf das umgebende Zend_Pdf-Dokument zu und uber $this->getPdf()->y auf die aktuelle vertikale Zeichenposition. Nach dem Aufruf von parent::draw() zieht man den Y-Wert um die Hohe der zusatzlichen Zeile weiter nach unten, damit nachfolgende Positionen nicht uberschrieben werden. Genau dieses Muster macht aus einem einzelnen Renderer eine wiederverwendbare Bausteinschicht fur die PDF-Generierung uber mehrere Produkttypen hinweg.
<?php
declare(strict_types=1);
namespace Mironsoft\PdfCustomizer\Model\Order\Pdf\Items\Invoice;
use Magento\Sales\Model\Order\Pdf\Items\Invoice\DefaultInvoice as CoreDefaultInvoice;
use Magento\Framework\Model\Context;
use Magento\Framework\Registry;
use Magento\Framework\Stdlib\StringUtils;
use Magento\Tax\Helper\Data as TaxHelper;
use Magento\Framework\Filter\FilterManager;
use Magento\Framework\Filesystem;
use Zend_Pdf_Page;
use Zend_Pdf_Color_Html;
/**
* Custom line item renderer that appends a tax breakdown row per item.
* Registered via etc/sales.xml as an alternative renderer for a specific product type.
*/
class DefaultInvoiceWithTaxBreakdown extends CoreDefaultInvoice
{
/**
* @param Context $context
* @param Registry $registry
* @param StringUtils $string
* @param TaxHelper $taxHelper
* @param FilterManager $filterManager
* @param Filesystem $filesystem
* @param bool $showQrReference Whether the payment reference line is rendered
* @param array $data
*/
public function __construct(
Context $context,
Registry $registry,
StringUtils $string,
TaxHelper $taxHelper,
FilterManager $filterManager,
Filesystem $filesystem,
private readonly bool $showQrReference = false,
array $data = []
) {
parent::__construct($context, $registry, $string, $taxHelper, $filterManager, $filesystem, $data);
}
/**
* Draw the default item block and append a tax breakdown line beneath it.
*
* @return Zend_Pdf_Page
*/
public function draw()
{
$page = parent::draw();
$item = $this->getItem();
$this->getPdf()->y -= 10;
$page->setFillColor(new Zend_Pdf_Color_Html('#64748b'));
$page->drawText(
sprintf('MwSt.-Satz: %s%% auf %s', $item->getOrderItem()->getTaxPercent(), $item->getOrderItem()->getName()),
35,
$this->getPdf()->y,
'UTF-8'
);
if ($this->showQrReference) {
$this->getPdf()->y -= 12;
}
return $page;
}
}
5. Zend_Pdf\Page und Zend_Pdf\Font fur eigene Inhalte
Wer eine zusatzliche Zeile auf einer Rechnung als PDF braucht, etwa eine Zahlungsreferenz oder einen QR-Code fur SEPA-Uberweisungen, arbeitet direkt mit den Zend_Pdf-Primitiven. Zend_Pdf_Page::drawText() zeichnet Text an exakten X/Y-Koordinaten, gemessen vom unteren linken Seitenrand in Points, wobei ein Point einem Zweiundsiebzigstel Zoll entspricht. Zend_Pdf_Font::fontWithName() ladt einen der eingebauten PDF-Standardfonts wie Helvetica oder Courier, ohne dass eine externe Font-Datei eingebunden werden muss.
Eine haufige Anforderung ist ein QR-Code mit Zahlungsreferenz unterhalb der Summenzeile. Da Zend_Pdf selbst keine QR-Code-Erzeugung mitbringt, generiert man das Bild vorab als PNG, ladt es per Zend_Pdf_Image::imageWithPath() und zeichnet es mit page->drawImage() an fester Position. Die Positionierung muss dabei die dynamische Y-Koordinate des ubergeordneten Renderings respektieren, sonst uberschneidet sich der QR-Code mit der letzten Positionszeile der PDF-Generierung.
Die dunne Preference-Klasse fur Pdf\Invoice ist der richtige Ort fur diese Erganzung, weil getPdf() bereits alle Seiten vollstandig aufgebaut hat und man am Ende jeder Seite gezielt zusatzlichen Text oder ein Bild einfugen kann, ohne das restliche Layout aus dem Core anzufassen. Constructor Property Promotion in PHP 8.4 halt die eigene Klasse dabei kompakt, auch wenn der geerbte Konstruktor selbst noch die klassische Parameterliste des Cores erwartet.
<?php
declare(strict_types=1);
namespace Mironsoft\PdfCustomizer\Model\Order\Pdf;
use Magento\Sales\Model\Order\Pdf\Invoice as CoreInvoice;
use Magento\Payment\Helper\Data as PaymentHelper;
use Magento\Payment\Model\Config as PaymentConfig;
use Magento\Framework\Stdlib\StringUtils;
use Magento\Framework\Filesystem;
use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Framework\Filter\FilterManager;
use Magento\Sales\Model\Order\Address\Renderer as AddressRenderer;
use Magento\Sales\Model\Order\Pdf\ItemsFactory;
use Magento\Sales\Model\Order\Pdf\Total\Factory as PdfTotalFactory;
use Magento\Framework\Stdlib\DateTime\TimezoneInterface;
use Magento\Sales\Model\Order\Pdf\Config as PdfConfig;
use Magento\Framework\Translate\Inline\StateInterface;
use Psr\Log\LoggerInterface;
use Zend_Pdf_Page;
use Zend_Pdf_Font;
/**
* Thin preference over the core invoice PDF renderer.
* Only appends a payment reference line, the core layout stays untouched.
*/
class Invoice extends CoreInvoice
{
/**
* @param PaymentConfig $paymentConfig
* @param StringUtils $string
* @param Filesystem $filesystem
* @param ScopeConfigInterface $scopeConfig
* @param FilterManager $filterManager
* @param AddressRenderer $addressRenderer
* @param ItemsFactory $pdfItemsFactory
* @param PdfTotalFactory $pdfTotalFactory
* @param TimezoneInterface $localeDate
* @param PdfConfig $pdfConfig
* @param StateInterface $inlineTranslation
* @param LoggerInterface $logger
* @param PaymentHelper $paymentHelper Custom dependency added by this preference
* @param array $data
*/
public function __construct(
PaymentConfig $paymentConfig,
StringUtils $string,
Filesystem $filesystem,
ScopeConfigInterface $scopeConfig,
FilterManager $filterManager,
AddressRenderer $addressRenderer,
ItemsFactory $pdfItemsFactory,
PdfTotalFactory $pdfTotalFactory,
TimezoneInterface $localeDate,
PdfConfig $pdfConfig,
StateInterface $inlineTranslation,
LoggerInterface $logger,
private readonly PaymentHelper $paymentHelper,
array $data = []
) {
parent::__construct(
$paymentConfig,
$string,
$filesystem,
$scopeConfig,
$filterManager,
$addressRenderer,
$pdfItemsFactory,
$pdfTotalFactory,
$localeDate,
$pdfConfig,
$inlineTranslation,
$logger,
$data
);
}
/**
* Extend core PDF generation with a payment reference line per invoice page.
*
* @param array $invoices
* @return \Zend_Pdf
*/
public function getPdf($invoices = [])
{
$pdf = parent::getPdf($invoices);
foreach ($pdf->pages as $page) {
/** @var Zend_Pdf_Page $page */
$page->setFont(Zend_Pdf_Font::fontWithName(Zend_Pdf_Font::FONT_COURIER), 8);
$page->drawText('Zahlungsreferenz: ' . $this->buildPaymentReference(), 25, 25, 'UTF-8');
}
return $pdf;
}
/**
* Build a deterministic payment reference string used for the QR line.
*
* @return string
*/
private function buildPaymentReference(): string
{
return sprintf('MSFT-%s', bin2hex(random_bytes(4)));
}
}
6. Logo, Kopf- und Fusszeile anpassen
Das Store-Logo auf Rechnungen als PDF wird uber insertLogo() gezeichnet, eine protected Methode, die den Pfad zum Logo aus der Store-Konfiguration liest und als Zend_Pdf_Image auf die erste Seite jeder Rechnung platziert. Eine haufige Anpassung: Das Logo soll je nach Store-View unterschiedlich sein oder rechtsbundig statt linksbundig erscheinen. Beides erfordert eine Erweiterung von insertLogo(), da die Methode protected ist und ein Plugin sie nicht erreicht.
Fur die Fusszeile gibt es in AbstractPdf keine dedizierte insertFooter()-Methode, stattdessen wird der Footer meist direkt am Ende von getPdf() gezeichnet, oft mit Seitenzahl und rechtlichen Hinweisen. Wer hier eine zusatzliche Zeile braucht, etwa einen Hinweis auf die Zahlungsfrist, sollte konsequent bei der gezielten Method-Extension bleiben und nicht die gesamte getPdf()-Methode kopieren, die in den Kernklassen mehrere hundert Zeilen umfasst.
Ein oft ubersehener Punkt: insertLogo() wird pro Seite unterschiedlich aufgerufen, abhangig vom Parameter isLastPage. Wer das Logo nur auf der ersten Seite, aber eine schlankere Kopfzeile auf Folgeseiten will, muss diesen Parameter in der eigenen Override-Methode auswerten, statt ihn zu ignorieren. Genau solche Details entscheiden, ob eine angepasste PDF-Generierung bei zwei- oder dreiseitigen Rechnungen korrekt aussieht.
7. Batch-Generierung per CLI-Command
Fur die nachtliche Archivierung aller Rechnungen als PDF eignet sich kein Observer und kein Cron-Job, der synchron im PHP-Prozess Hunderte PDFs erzeugt, sondern ein dedizierter bin/magento-CLI-Befehl. Ein eigenes Command, das Symfony\Component\Console\Command\Command erweitert, nimmt einen Zeitraum als Parameter entgegen, ladt die passenden Invoice-Objekte uber das InvoiceRepositoryInterface und ruft fur jede Rechnung getPdf() auf.
Der Vorteil gegenuber einem klassischen Cron-Skript: Der CLI-Befehl lasst sich uber crontab in der immer gleichen Docker- oder Server-Umgebung ausfuhren, unterstutzt Optionen wie --from und --to und kann ausfuhrlich protokollieren, wie viele Rechnungen als PDF verarbeitet wurden. Registriert wird der Befehl klassisch uber CommandListInterface in di.xml, nicht uber ein modul-spezifisches events.xml.
Bei sehr grossen Datenmengen sollte der Command in Chunks arbeiten, etwa zweihundert Rechnungen pro Batch, und zwischen den Batches den Objekt-Cache leeren, da Zend_Pdf-Objekte und die zugehorigen Invoice-Collections sonst den verfugbaren Speicher schnell ausschopfen. Diese Batch-Strategie ist die Grundlage fur jede grossere PDF-Generierung ausserhalb des Request-Lebenszyklus.
<?php
declare(strict_types=1);
namespace Mironsoft\PdfCustomizer\Console\Command;
use Magento\Sales\Api\InvoiceRepositoryInterface;
use Magento\Sales\Model\Order\Pdf\Invoice as InvoicePdf;
use Magento\Framework\Api\SearchCriteriaBuilder;
use Magento\Framework\Filesystem\DirectoryList;
use Magento\Framework\Filesystem;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Batch-generates invoice PDFs for a given date range and stores them for nightly archiving.
*/
class GenerateInvoicePdfBatch extends Command
{
private const OPTION_FROM = 'from';
private const OPTION_TO = 'to';
/**
* @param InvoiceRepositoryInterface $invoiceRepository
* @param SearchCriteriaBuilder $searchCriteriaBuilder
* @param InvoicePdf $invoicePdf
* @param Filesystem $filesystem
* @param string $name
*/
public function __construct(
private readonly InvoiceRepositoryInterface $invoiceRepository,
private readonly SearchCriteriaBuilder $searchCriteriaBuilder,
private readonly InvoicePdf $invoicePdf,
private readonly Filesystem $filesystem,
string $name = 'mironsoft:pdf:invoice-batch'
) {
parent::__construct($name);
}
/**
* Configure command name, description and CLI options.
*
* @return void
*/
protected function configure(): void
{
$this->setDescription('Generates archived invoice PDFs for a date range')
->addOption(self::OPTION_FROM, null, InputOption::VALUE_REQUIRED, 'Start date (Y-m-d)')
->addOption(self::OPTION_TO, null, InputOption::VALUE_REQUIRED, 'End date (Y-m-d)');
parent::configure();
}
/**
* Execute batch PDF generation and write archives to var/export/invoices.
*
* @param InputInterface $input
* @param OutputInterface $output
* @return int
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$criteria = $this->searchCriteriaBuilder
->addFilter('created_at', $input->getOption(self::OPTION_FROM), 'from')
->addFilter('created_at', $input->getOption(self::OPTION_TO), 'to')
->create();
$invoices = $this->invoiceRepository->getList($criteria)->getItems();
$directory = $this->filesystem->getDirectoryWrite(DirectoryList::VAR_DIR);
foreach ($invoices as $invoice) {
$pdf = $this->invoicePdf->getPdf([$invoice]);
$path = sprintf('export/invoices/invoice_%s.pdf', $invoice->getIncrementId());
$directory->writeFile($path, $pdf->render());
$output->writeln(sprintf('<info>Generated %s</info>', $path));
}
$output->writeln(sprintf('<info>%d invoice PDFs archived</info>', count($invoices)));
return Command::SUCCESS;
}
}
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
<!-- Register the batch command in the bin/magento command list -->
<type name="Magento\Framework\Console\CommandListInterface">
<arguments>
<argument name="commands" xsi:type="array">
<item name="pdf_invoice_batch" xsi:type="object">Mironsoft\PdfCustomizer\Console\Command\GenerateInvoicePdfBatch</item>
</argument>
</arguments>
</type>
</config>
8. Performance: synchron im Checkout vs. Queue
Die PDF-Generierung wahrend des Checkouts, direkt beim Speichern der Order, ist einer der haufigsten Performance-Fehler in Magento-Projekten. Zend_Pdf ist keine leichte Bibliothek: Das Aufbauen eines mehrseitigen Dokuments mit mehreren Renderer-Aufrufen kann je nach Positionsanzahl mehrere hundert Millisekunden dauern, Zeit, die der Kunde synchron im Checkout-Flow wartet, obwohl die PDF-Datei in diesem Moment gar nicht benotigt wird.
Der bessere Ansatz: Ein Plugin oder Observer auf das Auslosen der Rechnung veroffentlicht lediglich eine Nachricht in einer Message Queue, etwa mit der Invoice-ID als Payload. Ein separater Consumer verarbeitet diese Nachricht asynchron, generiert die Rechnung als PDF und speichert sie im Filesystem oder einer externen Ablage. Der Checkout selbst bleibt vollstandig unbelastet von der eigentlichen PDF-Generierung.
Diese Entkopplung zahlt sich besonders bei Lastspitzen aus, etwa im Sale-Geschaft: Statt dass hunderte parallele Checkout-Prozesse gleichzeitig PDF-Generierung anstossen und den PHP-FPM-Pool blockieren, verarbeitet ein oder mehrere Queue-Consumer die Anfragen kontrolliert nacheinander, mit klar konfigurierbarer Parallelitat uber die Anzahl der Consumer-Instanzen.
9. PDF-Generierung im direkten Vergleich
Nicht jede Anpassung an der PDF-Generierung braucht dieselbe Erweiterungstechnik. Die folgende Tabelle fasst die typischen Entscheidungspunkte zusammen, die in Projekten immer wieder zu falschen, weil zu invasiven, Losungen fuhren.
| Situation | Nicht empfohlen | Empfohlener Ansatz | Vorteil |
|---|---|---|---|
| Protected Methode andern (insertLogo) | Vollstandige Preference auf Pdf\Invoice | Gezielte Method-Extension | Weniger Merge-Konflikte bei Core-Updates |
| Zusatzliche Zeile pro Produkttyp | Renderer-Klasse komplett duplizieren | virtualType-Parametrisierung in di.xml | Kein doppelter Code |
| PDF-Generierung im Checkout | Synchron blockierend | Asynchron uber Message Queue | Keine Latenz fur den Kunden |
| Nachtlicher Massenexport | Manuelles Skript ohne Chunking | CLI-Command mit Batch-Processing | Kontrollierter Ressourcenverbrauch |
| Logging der Generierung | Preference nur fur Logging | Plugin auf offentliches getPdf() | Update-sicher, minimal invasiv |
In modernen Magento-Projekten ist die Kombination aus schmaler Preference, virtualType-Parametrisierung und Plugin fur periphere Logik die Konstellation, die sich am besten gegen kunftige Core-Updates behauptet. Wer stattdessen ganze Klassen kopiert, muss jedes Sicherheitsupdate manuell nachziehen, oft ohne zu bemerken, dass sich die Signatur einer geerbten Methode geandert hat.
10. Zusammenfassung
Die PDF-Generierung in Magento 2 folgt einer klaren Architektur: Pdf\AbstractPdf stellt geschutzte Bausteine bereit, Pdf\Invoice und Pdf\Shipment implementieren daraus konkrete Dokumente, und Pdf\Items-Renderer zeichnen einzelne Positionen. Weil diese Klassen konkret und teils protected sind, braucht jede tiefere Anpassung eine gezielte Preference statt eines Plugins, wahrend periphere Logik wie Logging oder Event-Trigger weiterhin uber Plugins auf offentliche Methoden lauft.
Wer Rechnungen als PDF um eigene Inhalte erweitert, zeichnet mit Zend_Pdf_Page und Zend_Pdf_Font direkt auf das fertige Dokument, registriert eigene Line-Item-Renderer uber virtualType in di.xml und vermeidet es, die vollstandige getPdf()-Methode zu kopieren. Fur Masse und Performance gilt: Batch-Generierung gehort in einen CLI-Command mit Chunking, nicht in ein Cron-Skript, und synchrone PDF-Generierung im Checkout gehort konsequent in eine Message Queue ausgelagert.
PDF-Generierung in Magento 2, das Wichtigste auf einen Blick
Gezielte Preference
Nur die eine betroffene protected Methode uberschreiben, den Rest mit parent:: an den Core delegieren. Minimiert Merge-Konflikte bei Updates.
Renderer via virtualType
Line-Item-Renderer per etc/sales.xml und di.xml virtualType parametrisieren, statt komplette Klassen zu duplizieren.
Zend_Pdf direkt nutzen
Zend_Pdf_Page und Zend_Pdf_Font fur QR-Codes, Zahlungsreferenzen und eigene Textpositionen auf Rechnungen als PDF.
Batch statt synchron
CLI-Commands fur nachtliche Archivierung, Message Queue statt synchroner PDF-Generierung im Checkout.
11. FAQ: PDF-Generierung in Magento 2
1Warum reicht ein Plugin nicht fur insertLogo?
2Pdf\AbstractPdf vs. Pdf\Invoice?
3QR-Code mit Zahlungsreferenz einfugen?
4Eigenen Renderer registrieren?
5Synchron oder per Queue?
6Viele Rechnungen als PDF im Bulk erzeugen?
7Relevante Zend_Pdf-Klassen?
8Performance bei grosser PDF-Generierung?
9Mehrere Preferences fur dieselbe Klasse?
10PDF-Generierung ohne echten Checkout testen?
Mironsoft
Magento 2 Entwicklung, Sales-Anpassungen und PDF-Rendering
Rechnungen als PDF, die zu eurem Prozess passen?
Wir analysieren eure bestehende PDF-Generierung, entwerfen eine gezielte Preference-Strategie und implementieren eigene Renderer, CLI-Batch-Exporte oder Queue-basierte Verarbeitung fur Rechnungen als PDF im Magento-Sales-Modul.
PDF-Audit
Analyse bestehender Preferences und Renderer auf unnotige Angriffsflache
Custom Rendering
QR-Codes, Steuerzeilen und individuelle Layouts fur Rechnungen als PDF
Batch & Queue
CLI-Commands und Queue-Consumer fur skalierbare PDF-Generierung