Import/Export-Profile in Magento 2 per Cron automatisieren
AI generated
M2
di.xml
Magento 2 · Import/Export & Automatisierung
Import/Export-Profile automatisieren
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.

13 Min. Lesezeit Cron-Automatisierung Import Model ErrorAggregator TransportBuilder Scheduled Operations

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.

11. FAQ: Automatisierte Import/Export-Profile in Magento 2: Das Wichtigste auf einen Blick

1Reicht ein einfacher Cronjob, der den Admin-Controller per HTTP aufruft?
Technisch funktioniert das, ist aber unnötig fragil und langsam, da unnötig Session- und Request-Overhead entsteht, der direkte programmatic Aufruf von Import::validateSource und Import::importSource ist der sauberere Weg.
2Wie oft sollte der zentrale Cronjob laufen, der die Profile prüft?
Ein Intervall von fünf bis zehn Minuten ist für die meisten Anwendungsfälle ausreichend granular, da die eigentliche Fälligkeit ohnehin über das gespeicherte cron_expression-Feld je Profil gesteuert wird.
3Was passiert, wenn zwei Cron-Läufe sich überschneiden?
Ohne Locking könnten beide Läufe dasselbe Profil parallel verarbeiten, was zu doppelt importierten Datensätzen oder Race Conditions führen kann, ein Cache-basiertes Lock mit kurzer TTL verhindert das zuverlässig.
4Sollte ein Import bei teilweise fehlerhaften Zeilen trotzdem laufen?
Das hängt vom Behavior-Modus und der fachlichen Anforderung ab, bei kritischen Datenquellen ist ein vollständiger Abbruch bei Validierungsfehlern meist sicherer als ein teilweiser Import mit stillschweigend übersprungenen Zeilen.
5Wie unterscheidet sich validateSource von importSource inhaltlich?
validateSource prüft die Quelldatei gegen das gewählte Schema, ohne Daten zu schreiben, importSource führt erst danach den eigentlichen Import durch, beide Schritte teilen sich denselben ErrorAggregator.
6Wie werden Admin-Nutzer bei einem fehlgeschlagenen Import zuverlässig informiert?
Über TransportBuilder mit einem eigenen E-Mail-Template und optional zusätzlich über einen Chat-Webhook, wichtig ist, dass beide Kanäle unabhängig voneinander funktionieren und ein Kanal-Ausfall den anderen nicht blockiert.
7Muss jedes automatisierte Profil eine eigene Datenbank-Entity haben?
Nicht zwingend, bei einer überschaubaren, stabilen Anzahl von Profilen reicht auch eine Konfiguration über system.xml, eine eigene Entity lohnt sich vor allem, wenn Profile häufig über den Admin-Bereich angelegt oder geändert werden.
8Wie wird verhindert, dass ein hängender Import den nächsten Cron-Lauf blockiert?
Das Lock sollte eine TTL besitzen, die deutlich über der erwarteten maximalen Laufzeit liegt, aber nicht unbegrenzt ist, damit ein tatsächlich abgestürzter Prozess das Profil nicht dauerhaft blockiert.
9Wo wird die Lauf-Historie sinnvollerweise gespeichert?
In einer eigenen, über db_schema.xml deklarierten Tabelle mit Referenz auf das jeweilige Profil, dargestellt über ein eigenes Admin-Grid, das gezielt nach Status und Zeitraum gefiltert werden kann.
10Ist diese Automatisierungsschicht mit einer performance-optimierten Import-Implementierung für sehr große Kataloge kompatibel?
Ja, die Automatisierungsschicht steuert lediglich den Zeitpunkt, die Fehlerauswertung und die Benachrichtigung, die eigentliche Import-Performance bleibt ein unabhängiges Thema und kann separat optimiert werden.