Vom manuellen Trigger zum zuverlässigen Cronjob mit Fehlerbehandlung und Benachrichtigung
Der manuelle Klick auf Importieren im Admin-Bereich funktioniert für einmalige Datenmigrationen gut, für wiederkehrende Lieferanten-Feeds oder tägliche Bestandsabgleiche ist er dagegen ein Betriebsrisiko: Vergisst jemand den Klick, bleiben Daten veraltet, ohne dass es jemand bemerkt. Wer Magentos Import-Model programmatic über einen eigenen Cronjob ansteuert, bekommt Automatisierung, Fehlerauswertung und Benachrichtigung aus einer Hand, ganz ohne Drittanbieter-Erweiterung.
Inhaltsverzeichnis
- 1. Warum manuelles Triggern für wiederkehrende Importe nicht reicht
- 2. Eine eigene Scheduled-Operation-Entity anlegen
- 3. Den periodischen Trigger als Cronjob registrieren
- 4. Magentos Import-Model programmatic statt über den Admin-Controller aufrufen
- 5. Fehlerergebnisse über den ErrorAggregator auswerten
- 6. Benachrichtigung bei fehlgeschlagenen automatisierten Importen
- 7. Lauf-Historie für Nachvollziehbarkeit protokollieren
- 8. Abgrenzung zur Performance-Optimierung großer Kataloge
- 9. Zugangsdaten und Quelldateien sicher handhaben
- 10. Zusammenfassung
- 11. FAQ
1. Warum manuelles Triggern für wiederkehrende Importe nicht reicht
Magentos Standard-Import/Export-Bereich ist bewusst als manuell auszulösender Werkzeugkasten konzipiert: Ein Admin-Nutzer wählt eine Entität, lädt eine Datei hoch, prüft das Validierungsergebnis und startet den eigentlichen Import erst nach bewusster Bestätigung. Für einmalige Migrationen ist dieser Ablauf genau richtig, für einen täglichen Lieferanten-Feed mit mehreren tausend Zeilen wird er schnell zum Flaschenhals, weil die Automatisierung an der fehlenden Möglichkeit scheitert, denselben Ablauf ohne menschliches Zutun regelmäßig anzustoßen.
Dieser Artikel behandelt bewusst nicht die Performance-Optimierung großer Importe selbst, dazu gibt es bereits eine eigene Betrachtung, sondern ausschließlich die Frage, wie ein bestehendes oder neues Import-Profil zuverlässig zeitgesteuert läuft, sein Ergebnis dokumentiert und im Fehlerfall die richtigen Personen informiert, statt stillschweigend zu scheitern.
2. Eine eigene Scheduled-Operation-Entity anlegen
Statt Importkonfiguration und Dateipfad fest im Code zu verdrahten, lohnt sich eine eigene, über db_schema.xml deklarierte Entity, die pro automatisiertem Profil Entitätstyp, Quelldateipfad oder SFTP-Verzeichnis, Zeitplan-Ausdruck und den Status des letzten Laufs speichert. Diese Entity wird über ein eigenes Repository nach den üblichen Service-Contract-Konventionen verwaltet und im Admin-Bereich über ein eigenes UI-Component-Grid gepflegt, sodass neue automatisierte Profile ohne Code-Deployment angelegt werden können.
Der Zeitplan selbst wird nicht als starre Cron-Expression im Modul-Code, sondern als konfigurierbares Feld pro Profil gespeichert, wodurch unterschiedliche Profile unterschiedlich häufig laufen können, ohne dass für jede neue Frequenz ein eigener Cronjob-Eintrag in crontab.xml notwendig wird. Der eigentliche Cronjob läuft dagegen in einem festen, kurzen Intervall und prüft bei jedem Lauf selbst, welche Profile gemäß ihrem gespeicherten Zeitplan fällig sind.
<!-- app/code/Mironsoft/ScheduledImportExport/etc/db_schema.xml -->
<table name="mironsoft_scheduled_import_profile" resource="default" engine="innodb">
<column xsi:type="int" name="entity_id" identity="true" nullable="false"/>
<column xsi:type="varchar" name="entity_type" length="64" nullable="false"/>
<column xsi:type="varchar" name="source_path" length="255" nullable="false"/>
<column xsi:type="varchar" name="cron_expression" length="32" nullable="false"/>
<column xsi:type="varchar" name="last_status" length="32" nullable="true"/>
<column xsi:type="timestamp" name="last_run_at" nullable="true"/>
<constraint xsi:type="primary" referenceId="PRIMARY">
<column name="entity_id"/>
</constraint>
</table>
3. Den periodischen Trigger als Cronjob registrieren
Der eigentliche Cronjob wird wie jeder andere über crontab.xml und cron_groups.xml registriert und läuft üblicherweise alle fünf oder zehn Minuten, deutlich häufiger als jedes einzelne Profil tatsächlich importiert. Innerhalb der Job-Klasse wird bei jedem Lauf zunächst geprüft, welche gespeicherten Profile gemäß ihrem cron_expression-Feld gerade fällig sind, üblicherweise über eine kleine Wrapper-Bibliothek, die eine Cron-Expression gegen den aktuellen Zeitpunkt auswertet.
Entscheidend für den zuverlässigen Betrieb ist ein Locking-Mechanismus, der verhindert, dass zwei sich überschneidende Cron-Läufe dasselbe Profil parallel verarbeiten, etwa wenn ein Import ungewöhnlich lange läuft und der nächste reguläre Cron-Tick bereits startet. Ein einfacher, aber robuster Ansatz nutzt einen Cache-Eintrag mit kurzer TTL als Lock, der beim Start eines Profil-Imports gesetzt und nach Abschluss oder im finally-Block wieder freigegeben wird.
<?php
declare(strict_types=1);
namespace Mironsoft\ScheduledImportExport\Cron;
use Mironsoft\ScheduledImportExport\Model\ResourceModel\Profile\CollectionFactory;
use Mironsoft\ScheduledImportExport\Model\ProfileImportRunner;
use Mironsoft\ScheduledImportExport\Model\ProfileLockManager;
use Mironsoft\ScheduledImportExport\Model\CronExpressionEvaluator;
/**
* Prüft periodisch fällige Import-Profile und stößt
* deren Verarbeitung unter Locking-Schutz an.
*/
class RunDueImportProfiles
{
/**
* @param CollectionFactory $profileCollectionFactory Liefert alle gespeicherten Profile
* @param CronExpressionEvaluator $cronEvaluator Prueft Faelligkeit gegen die Cron-Expression
* @param ProfileLockManager $lockManager Verhindert parallele Verarbeitung desselben Profils
* @param ProfileImportRunner $importRunner Fuehrt den eigentlichen Import aus
*/
public function __construct(
private readonly CollectionFactory $profileCollectionFactory,
private readonly CronExpressionEvaluator $cronEvaluator,
private readonly ProfileLockManager $lockManager,
private readonly ProfileImportRunner $importRunner,
) {
}
/**
* Fuehrt den periodischen Check aller Profile aus.
*
* @return void
*/
public function execute(): void
{
$collection = $this->profileCollectionFactory->create();
foreach ($collection as $profile) {
if (!$this->cronEvaluator->isDue($profile->getCronExpression())) {
continue;
}
if (!$this->lockManager->acquire((int) $profile->getId())) {
continue;
}
try {
$this->importRunner->run($profile);
} finally {
$this->lockManager->release((int) $profile->getId());
}
}
}
}
4. Magentos Import-Model programmatic statt über den Admin-Controller aufrufen
Der eigentliche Import läuft über dieselbe Kernlogik, die auch der Admin-Controller nutzt, nämlich Magento\ImportExport\Model\Import. Programmatic angesteuert entfällt der Umweg über HTTP-Request und Session, der Ablauf gliedert sich in zwei getrennte Schritte: validateSource prüft die Quelldatei gegen das gewählte Entitäts- und Verhaltensschema, ohne bereits Daten zu schreiben, und importSource führt den eigentlichen Import erst nach erfolgreicher Validierung aus.
Diese Zweiteilung ist bewusst so gestaltet und sollte auch im automatisierten Ablauf beibehalten werden: Ein Profil, dessen Quelldatei offensichtlich fehlerhaft ist, etwa durch ein geändertes Spaltenlayout des Lieferanten, sollte niemals teilweise importiert werden. Der automatisierte Runner bricht deshalb bei einem harten Validierungsfehler vollständig ab, statt nur die validen Zeilen zu übernehmen und den Rest stillschweigend zu verwerfen.
<?php
declare(strict_types=1);
namespace Mironsoft\ScheduledImportExport\Model;
use Magento\ImportExport\Model\Import;
use Magento\ImportExport\Model\Import\Source\Csv;
/**
* Führt einen einzelnen automatisierten Import-Lauf über
* Magentos Import-Model programmatic aus.
*/
class ProfileImportRunner
{
/**
* @param Import $import Magento-Kern-Import-Model
* @param ImportResultNotifier $resultNotifier Wertet Ergebnis aus und benachrichtigt bei Fehlern
*/
public function __construct(
private readonly Import $import,
private readonly ImportResultNotifier $resultNotifier,
) {
}
/**
* Führt Validierung und Import für ein einzelnes Profil aus.
*
* @param \Mironsoft\ScheduledImportExport\Model\Profile $profile Zu verarbeitendes Profil
* @return void
*/
public function run(Profile $profile): void
{
$this->import->setData([
'entity' => $profile->getEntityType(),
'behavior' => Import::BEHAVIOR_APPEND,
]);
$source = new Csv($profile->getSourcePath(), $this->import->getWorkingDir());
$validationResult = $this->import->validateSource($source);
if (!$validationResult) {
$this->resultNotifier->notifyFailure($profile, $this->import->getErrorAggregator());
return;
}
$this->import->importSource();
$this->resultNotifier->notifyResult($profile, $this->import->getErrorAggregator());
}
}
5. Fehlerergebnisse über den ErrorAggregator auswerten
Sowohl validateSource als auch importSource sammeln aufgetretene Probleme im ErrorAggregator statt eine einzelne Exception zu werfen, was bei Massendaten sinnvoll ist, da ein einzelner defekter Datensatz nicht automatisch den gesamten Import verhindern soll. Der Aggregator unterscheidet Fehler nach Schweregrad, kritische Fehler, die den Import komplett stoppen, und Zeilen-Fehler, die einzelne fehlerhafte Datensätze markieren, den restlichen Import aber nicht blockieren.
Für die automatisierte Auswertung reicht ein einfaches hasFatalExceptions oder getErrorsCount am Aggregator nicht aus, um dem Empfänger einer Fehlerbenachrichtigung zu sagen, was tatsächlich schiefgelaufen ist. Sinnvoller ist es, die einzelnen Fehlermeldungen über getAllErrors mit Zeilennummer und Fehlertext auszulesen und in einer kompakten, für Menschen lesbaren Zusammenfassung in die Benachrichtigung aufzunehmen, statt nur eine generische Fehlgeschlagen-Meldung zu verschicken.
6. Benachrichtigung bei fehlgeschlagenen automatisierten Importen
Ein automatisierter Import, der stillschweigend scheitert, ist gefährlicher als ein Import, der gar nicht erst automatisiert wurde, weil das Vertrauen in aktuelle Daten trügerisch bleibt. Die Benachrichtigung erfolgt über Magento\Framework\Mail\Template\TransportBuilder mit einem eigenen E-Mail-Template, das Profilname, Zeitpunkt, Anzahl verarbeiteter und fehlgeschlagener Zeilen sowie die wichtigsten Fehlermeldungen aus dem ErrorAggregator enthält.
Für Teams mit bestehender Slack- oder Teams-Integration lohnt sich zusätzlich ein einfacher Webhook-Aufruf parallel zur E-Mail, da E-Mail-Benachrichtigungen im Alltag leicht übersehen werden, ein Chat-Hinweis dagegen meist deutlich schneller wahrgenommen wird. Beide Kanäle sollten unabhängig voneinander fehlschlagen dürfen, ein nicht erreichbarer Webhook darf niemals den Versand der E-Mail-Benachrichtigung verhindern.
7. Lauf-Historie für Nachvollziehbarkeit protokollieren
Jeder automatisierte Lauf sollte unabhängig vom Ergebnis in einer eigenen Historie-Tabelle protokolliert werden, mit Start- und Endzeitpunkt, Ergebnis-Status und einer Referenz auf die vollständige Fehlerliste. Ohne diese Historie lässt sich im Nachhinein kaum rekonstruieren, ob ein bestimmter automatisierter Import an einem bestimmten Tag überhaupt gelaufen ist oder aus welchem Grund er ausgefallen ist, was besonders bei sporadisch auftretenden Netzwerkproblemen zum Lieferanten-FTP-Server wichtig wird.
Ein eigenes Admin-Grid über dieser Historie-Tabelle, aufgebaut nach demselben UI-Component-Muster wie jedes andere Grid im Projekt, macht die Historie für Admin-Nutzer ohne Datenbankzugriff nachvollziehbar und erlaubt es, gezielt nach fehlgeschlagenen Läufen eines bestimmten Profils zu filtern, ganz ohne zusätzliches Reporting-Tool.
8. Abgrenzung zur Performance-Optimierung großer Kataloge
Dieser Artikel behandelt bewusst nicht, wie ein einzelner Import mit mehreren Millionen Zeilen selbst performant gestaltet wird, batch-weise Verarbeitung, Indizierungs-Strategie während des Imports oder Speicherverbrauch großer CSV-Dateien sind ein eigenes Thema mit eigenen Lösungsmustern. Die hier beschriebene Automatisierungsschicht funktioniert unabhängig von der Größe des einzelnen Imports und lässt sich mit einer performance-optimierten Import-Implementierung kombinieren, ohne dass sich die beiden Ebenen gegenseitig beeinflussen.
In der Praxis bedeutet das: Wer bereits eine performante Import-Pipeline für große Kataloge besitzt, muss für die Automatisierung lediglich den Aufruf-Zeitpunkt, die Fehlerauswertung und die Benachrichtigung ergänzen, ohne die eigentliche Import-Logik anzufassen. Die beiden Themenfelder ergänzen sich, sind aber technisch klar voneinander getrennt.
9. Zugangsdaten und Quelldateien sicher handhaben
Automatisierte Profile greifen häufig auf externe Quellen zu, ein SFTP-Verzeichnis eines Lieferanten oder einen API-Endpunkt mit eigenem Zugangstoken, und diese Zugangsdaten dürfen niemals als Klartext-Spalte in der Scheduled-Operation-Entity landen. Magentos eigener Mechanismus für verschlüsselte Konfigurationswerte, ansprechbar über EncryptorInterface, eignet sich auch für Werte, die nicht über system.xml, sondern über eine eigene Entity verwaltet werden, solange Verschlüsselung und Entschlüsselung konsequent an einer einzigen Stelle im Code gekapselt sind.
Ebenso wichtig ist eine klare Trennung zwischen dem Verzeichnis, in das Lieferanten-Dateien abgelegt werden, und dem übrigen Dateisystem des Shops. Ein Import-Profil, das versehentlich auf einen beliebigen, vom Nutzer eingegebenen Pfad zugreifen kann, öffnet eine Path-Traversal-Angriffsfläche, weshalb der Quellpfad serverseitig gegen ein festes Basisverzeichnis validiert werden sollte, statt Nutzereingaben ungeprüft an das Dateisystem weiterzureichen.
| Baustein | Magento-Klasse / Mechanismus | Aufgabe | Häufiger Fehler |
|---|---|---|---|
| Zeitplan-Verwaltung | eigene Entity + CronExpressionEvaluator | Fälligkeit je Profil bestimmen | starre Cron-Expression statt konfigurierbarem Feld |
| Locking | CacheInterface mit kurzer TTL | parallele Verarbeitung verhindern | fehlendes Lock bei überlappenden Läufen |
| Validierung | Import::validateSource | Datei vor Import prüfen | Import trotz fehlgeschlagener Validierung fortsetzen |
| Import-Ausführung | Import::importSource | eigentlichen Datenimport durchführen | Behavior-Modus falsch gewählt |
| Fehlerauswertung | ErrorAggregator::getAllErrors | verständliche Fehlerdetails extrahieren | nur generischen Fehlgeschlagen-Status prüfen |
| Benachrichtigung | TransportBuilder + optionaler Webhook | richtige Personen informieren | Benachrichtigung nur bei Erfolg statt bei Fehler |
Mironsoft
Magento-Entwicklung, Modul-Beratung und Systemarchitektur
Magento-Projekt, das eine zweite Meinung oder erfahrene Umsetzung braucht?
Wir entwickeln individuelle Magento-Module, beraten bei Architekturentscheidungen und übernehmen komplexe Umsetzungen, von der Service-Contract-Planung bis zum produktionsreifen Deployment.
Architektur-Beratung
Modul- und Systemarchitektur vor der Umsetzung fundiert durchdenken lassen.
Custom-Modul-Entwicklung
Individuelle Magento-Module nach Best Practices sauber umsetzen.
Code-Review & Audit
Bestehende Module auf Performance, Sicherheit und Wartbarkeit prüfen lassen.
10. Zusammenfassung
Automatisierte Import/Export-Profile in Magento 2: Das Wichtigste auf einen Blick
Datenmodell
Eigene Scheduled-Operation-Entity mit konfigurierbarem Zeitplan statt fest kodierter Cron-Expression.
Ausführung
Import::validateSource und Import::importSource programmatic aufrufen, bei Validierungsfehlern vollständig abbrechen.
Fehlerbehandlung
ErrorAggregator granular auswerten und detaillierte, nicht generische Fehlerinformationen weitergeben.
Betrieb
Locking gegen parallele Läufe, Lauf-Historie protokollieren, E-Mail- und Chat-Benachrichtigung unabhängig voneinander.