Magento 2: Produktimport direkt in die Datenbank

Im vorherigen Artikel Ich habe den Prozess des Produktimports in Magento 2 auf herkömmliche Weise beschrieben – über Modelle und Repositories. Die übliche Methode weist eine sehr niedrige Datenverarbeitungsgeschwindigkeit auf. Auf meinem Laptop war es etwa ein Produkt pro Sekunde. In diesem Fortsetzung betrachte ich eine alternative Methode des Produktimports – die direkte Eintragung in die Datenbank unter Umgehung der Standardmechanismen von Magento 2 (Modelle, Fabriken, Repositories). Die Reihenfolge der Schritte, die den Import von Produkten sicherstellt, kann für jede Programmiersprache angepasst werden, die mit MySQL arbeiten kann.

Haftungsausschluss: In Magento gibt es eine fertige Funktionalität für den Import von Daten und wahrscheinlich werden Sie damit auskommen. Wenn Sie jedoch mehr Kontrolle über den Importprozess benötigen, der nicht nur die Vorbereitung einer CSV-Datei umfasst, kommen Sie bitte unter die Haube.

Magento 2: Produktimport direkt in die Datenbank

Den aus dem Schreiben beider Artikel resultierenden Code können Sie im Magento-Modul „flancer32/mage2_ext_demo_import„ einsehen. Hier sind einige Einschränkungen, die ich beachtet habe, um den Demo-Modulkode zu vereinfachen:

  • Produkte werden nur erstellt, nicht aktualisiert.
  • Ein Lager
  • Es werden nur die Namen der Kategorien importiert, ohne deren Struktur
  • Die Datenstrukturen entsprechen der Version 2.3

JSON zum Import eines einzelnen Produkts:

{
  "sku": "MVA20D-UBV-3",
  "name": "Verschlussschutz für die Versiegelung VA47-29 IEK",
  "desc": "Zugriff auf Geräte sicherstellen ...",
  "desc_short": "Der Verschlussschutz für die Versiegelung VA47-29 IEK ist vorgesehen für ...",
  "price": 5.00,
  "qty": 25,
  "categories": ["Kategorie 1", "Kategorie 2"],
  "image_path": "mva20d_ubv_3.png"
}

Übersicht der Hauptphasen des Imports

  • Registrierung des Produkts selbst
  • Verknüpfung des Produkts mit der Website
  • Grundattribute des Produkts (EAV)
  • Bestandsdaten (Menge des Produkts im Lager)
  • Medien (Bilder)
  • Verknüpfung mit den Kategorien im Katalog

Produktregistrierung

Die grundlegenden Informationen über das Produkt finden sich in catalog_product_entity:

CREATE TABLE `catalog_product_entity` (
  `entity_id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'Entity Id',
  `attribute_set_id` smallint(5) unsigned NOT NULL DEFAULT '0' COMMENT 'Attribute Set ID',
  `type_id` varchar(32) NOT NULL DEFAULT 'simple' COMMENT 'Type ID',
  `sku` varchar(64) DEFAULT NULL COMMENT 'SKU',
  `has_options` smallint(6) NOT NULL DEFAULT '0' COMMENT 'Hat Optionen',
  `required_options` smallint(5) unsigned NOT NULL DEFAULT '0' COMMENT 'Erforderliche Optionen',
  `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Erstellungszeit',
  `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'Änderungszeit',
  PRIMARY KEY (`entity_id`),
  KEY `CATALOG_PRODUCT_ENTITY_ATTRIBUTE_SET_ID` (`attribute_set_id`),
  KEY `CATALOG_PRODUCT_ENTITY_SKU` (`sku`)
)

Die minimal erforderlichen Informationen zur Erstellung eines Eintrags im Produktverzeichnis:

  • attribut_set_id
  • sku

zusätzlich:

  • typ_id — wenn wir nichts angeben, wird 'simple' verwendet

Für die direkte Datenbankaufzeichnung verwende ich den DB-Adapter von Magento selbst:

function create($sku, $typeId, $attrSetId)
{
    \/** @var MagentoFrameworkAppResourceConnection $this->resource *\/ 
    \/** @var MagentoFrameworkDBAdapterPdoMysql $conn *\/ 
    $conn = $this->resource->getConnection();
    $table = $this->resource->getTableName('catalog_product_entity');
    $bind = [
        'sku' => $sku,
        'type_id' => $typeId,
        'attribute_set_id' => $attrSetId
    ];
    $conn->insert($table, $bind);
    $result = $conn->lastInsertId($table);
    return $result;
}

Nach der Registrierung des Produkts in catalog_product_entity wird es im Admin-Bereich sichtbar, im Produktgitter (Katalog / Produkte).

Magento 2: Produktimport direkt in die Datenbank

Verbindung zwischen Produkt und Website

Die Verbindung des Produkts mit der Website bestimmt, in welchen Geschäften und auf welchen Schaufenstern das Produkt im Frontend verfügbar sein wird.

function linkToWebsite($prodId, $websiteId)
{
    \/** @var MagentoFrameworkAppResourceConnection $this->resource *\/ 
    \/** @var MagentoFrameworkDBAdapterPdoMysql $conn *\/ 
    $conn = $this->resource->getConnection();
    $table = $this->resource->getTableName('catalog_product_website');
    $bind = [
        'product_id' => $prodId,
        'website_id' => $websiteId
    ];
    $conn->insert($table, $bind);
}

Magento 2: Produktimport direkt in die Datenbank

Basisattribute des Produkts

Ein frisch registriertes Produkt hat zunächst keinen Namen und keine Beschreibung. All das geschieht über EAV-Attribute. Hier ist die Liste der Basisattribute des Produkts, die benötigt werden, damit das Produkt im Frontend ausreichend korrekt angezeigt wird:

  • name
  • Preis
  • description
  • kurze_beschreibung
  • status
  • steuer_klasse_id
  • url_key
  • sichtbarkeit

Ein einzelnes Attribut wird so zum Produkt hinzugefügt (Details zur Abfrage der ID und des Typs des Attributs anhand seines Codes sind ausgelassen):

public function create($prodId, $attrCode, $attrValue)
{
    $attrId = \/\* Attribut-ID anhand des Attributcodes abrufen *\/ 
    $attrType = \/\* Attributtyp [datetime|decimal|int|text|varchar] anhand des Attributcodes abrufen *\/ 
    if ($attrId) {
        \/** @var MagentoFrameworkAppResourceConnection $this->resource *\/ 
        \/** @var MagentoFrameworkDBAdapterPdoMysql $conn *\/ 
        $conn = $this->resource->getConnection();
        $tblName = 'catalog_product_entity_' . $attrType;
        $table = $this->resource->getTableName($tblName);
        $bind = [
            'attribute_id' => $attrId,
            'entity_id' => $prodId,
            \/\* Alle Attribute im Standard-Store-View mit id=0 (admin) speichern *\/ 
            'store_id' => 0,
            'value' => $attrValue
        ];
        $conn->insert($table, $bind);
    }
}

Durch den Attributcode bestimmen wir seine ID und Datentyp (datetime, decimal, int, text, varchar), dann schreiben wir die Daten in die entsprechende Tabelle für die administrative Ansicht (store_id = 0).

Nach dem Hinzufügen der oben genannten Attribute zum Produkt sieht das im Admin-Bereich wie folgt aus:

Magento 2: Produktimport direkt in die Datenbank

Inventardaten

Seit Version 2.3 von Magento gibt es parallel zwei Satz von Tabellen, die die Speicherung von Bestandsinformationen (Produktmenge) gewährleisten:

  • cataloginventory_*: alte Struktur;
  • inventory_*: neue Struktur (MSI – Multi Source Inventory);

Inventardaten müssen in beide Strukturen hinzugefügt werden, da die neue Struktur derzeit noch nicht vollständig unabhängig von der alten ist (es scheint sehr wahrscheinlich, dass für default das Lager in der neuen Struktur die Tabelle cataloginventory_stock_status als inventory_stock_1).

cataloginventory_

Bei der Bereitstellung von Magneto 2.3 haben wir zunächst 2 Einträge in store_website, was zwei Websites entspricht – einer administrativen und einer Hauptkundenwebsite:

website_id|code |name        |sort_order|default_group_id|is_default|
----------|-----|------------|----------|----------------|----------|
         0|admin|Admin       |         0|               0|         0|
         1|base |Hauptwebsite|         0|               1|         1|

In der Tabelle cataloginventory_stock haben wir nur einen Eintrag:

stock_id|website_id|stock_name|
--------|----------|----------|
       1|         0|Standard  |

Das heißt, in der alten Struktur haben wir nur ein 'Lager' (stock) und es ist an die administrative Website gebunden. Das Hinzufügen neuer sources/stocks in MSI (der neuen Struktur) führt nicht dazu, dass neue Einträge in cataloginventory_stock.

Die Inventardaten zu den Produkten in der alten Struktur werden ursprünglich in den Tabellen festgehalten:

  • cataloginventory_stock_item
  • cataloginventory_stock_status

cataloginventory_stock_item

function createOldItem($prodId, $qty)
{
    $isQtyDecimal = (((int)$qty) != $qty);
    $isInStock = ($qty > 0);
    
    /** @var MagentoFrameworkAppResourceConnection $this->resource */
    /** @var MagentoFrameworkDBAdapterPdoMysql $conn */
    $conn = $this->resource->getConnection();
    $table = $this->resource->getTableName('cataloginventory_stock_item');
    $bind = [
        'product_id' => $prodId,
        /* wir verwenden nur ein Lager in der 'cataloginventory' Struktur standardmäßig */
        'stock_id' => 1,
        'qty' => $qty,
        'is_qty_decimal' => $isQtyDecimal,
        'is_in_stock' => $isInStock,
        /* standardlager ist an die Verwaltungswebsite gebunden (siehe `cataloginventory_stock`) */
        'website_id' => 0
    ];
    $conn->insert($table, $bind);
}

cataloginventory_stock_status

function createOldStatus($prodId, $qty)
{
    $isInStock = ($qty > 0);
    
    /** @var MagentoFrameworkAppResourceConnection $this->resource */
    /** @var MagentoFrameworkDBAdapterPdoMysql $conn */
    $conn = $this->resource->getConnection();
    $table = $this->resource->getTableName('cataloginventory_stock_status');
    $bind = [
        'product_id' => $prodId,
        /* wir verwenden nur ein Lager in der 'cataloginventory' Struktur standardmäßig */
        'stock_id' => 1,
        'qty' => $qty,
        'stock_status' => MagentoCatalogInventoryApiDataStockStatusInterface::STATUS_IN_STOCK,
        /* standardlager ist an die Verwaltungswebsite gebunden (siehe `cataloginventory_stock`) */
        'website_id' => 0 
    ];
    $conn->insert($table, $bind);
}

inventory_

Ursprünglich enthält die neue Struktur zur Speicherung von Inventardaten 1 'Quelle» (inventory_source):

source_code|name          |enabled|description   |latitude|longitude|country_id|...|
-----------|--------------|-------|--------------|--------|---------|----------|...|
default    |Standardquelle |      1|Standardquelle |0.000000| 0.000000|US        |...|

und ein 'lager» (inventory_stock):

stock_id|name         |
--------|-------------|
       1|Standardlager |

«Quelle» ist ein physischer Speicher für Produkte (der Eintrag enthält physische Koordinaten und eine postalische Adresse). «Lager» stellt eine logische Verbindung mehrerer „Quellen“ dar (inventory_source_stock_link)

link_id|stock_id|source_code|priority|
-------|--------|-----------|--------|
      1|       1|default    |       1|

auf der Ebene, wo die Bindung an den Verkaufskanal stattfindet (inventory_stock_sales_channel)

type   |code|stock_id|
-------|----|--------|
website|base|       1|

Aufgrund der Datenstruktur sind verschiedene Typen von Verkaufskanälen vorgesehen, aber standardmäßig wird nur die Verbindung „stock«-«, aber in Zukunft planen wir, verschiedene Kopien» verwendet (der Link zur Webseite erfolgt über den Code der Webseite — base).

Ein „lager» kann mit mehreren „Quellen« verbunden sein, während ein „Quelle» mit mehreren „Lagern» verknüpft sein kann (viele-zu-viele Beziehung). Die Ausnahme bilden die Default-„Quelle» und „lager«. Sie werden nicht an andere Entitäten gebunden (Einschränkung auf Codierungsebene — es tritt ein Fehler auf: „Can not save link related to Default Source or Default Stock„). Eine detailliertere Beschreibung der MSI-Struktur in Magento 2 kann in dem Artikel „Lagerverwaltungssystem mit CQRS und Event Sourcing. Design«.

Ich werde die Default-Konfiguration verwenden und alle Lagerinformationen der Quelle hinzufügen default, die im Verkaufskanal verwendet wird, der mit der Webseite mit dem Code base verknüpft ist (entspricht dem Kundenbereich des Shops — siehe. store_website):

function createNewItem($sku, $qty)
{
    \/** @var MagentoFrameworkAppResourceConnection $this->resource *\/\n    \/** @var MagentoFrameworkDBAdapterPdoMysql $conn *\/\n    $conn = $this->resource->getConnection();\n    $table = $this->resource->getTableName('inventory_source_item');\n    $bind = [\n        'source_code' => 'default',\n        'sku' => $sku,\n        'quantity' => $qty,\n        'status' => MagentoInventoryApiApiDataSourceItemInterface::STATUS_IN_STOCK\n    ];\n    $conn->insert($table, $bind);\n}

Nach dem Hinzufügen von Lagerdaten zum Produkt im Admin-Bereich ergibt sich folgendes Bild:

Magento 2: Produktimport direkt in die Datenbank

Medien

Bei der „manuellen“ Hinzufügung von Bildern zum Produkt über das Admin-Panel werden die entsprechenden Informationen in den folgenden Tabellen gespeichert:

  • catalog_product_entity_media_gallery: Medienregister (Bilder und Video-Dateien);
  • catalog_product_entity_media_gallery_value: Verknüpfung von Medien mit Produkten und Shops (lokalisierung);
  • catalog_product_entity_media_gallery_value_to_entity: Verknüpfung von Medien nur mit Produkten (wahrscheinlich Default-Medieninhalt für das Produkt);
  • catalog_product_entity_varchar: hier werden Rollen gespeichert, in denen das Bild verwendet wird;

und die eigentlichen Bilder werden im Katalog gespeichert .\/pub\/media\/catalog\/product\/x\/y\/

x und y — der erste und zweite Buchstabe des Dateinamens des Bildes. Zum Beispiel sollte die Datei image.png gespeichert werden als ./pub/media/catalog/product/i/m/image.png, damit die Plattform es als Bild bei der Beschreibung von Produkten aus dem Katalog verwenden kann.

Registrieren Sie die in .\/pub\/media\/catalog\/product\/ Mediumdatei (der Prozess der Platzierung der Datei in diesem Artikel wird nicht behandelt):

function createMediaGallery($imgPathPrefixed)
{
    $attrId = \/\* holen Sie sich die Attribut-ID anhand des Attributcodes 'media_gallery' *\/ 
    \/** @var MagentoFrameworkAppResourceConnection $this->resource *\/ 
    \/** @var MagentoFrameworkDBAdapterPdoMysql $conn *\/ 
    $conn = $this->resource->getConnection();
    $table = $this->resource->getTableName('catalog_product_entity_media_gallery');
    $bind = [
        'attribute_id' => $attrId,
        'value' => $imgPathPrefixed,
        \/\* 'image' oder 'video' *\/ 
        'media_type' => 'image',
        'disabled' => false
    ];
    $conn->insert($table, $bind);
    $result = $conn->lastInsertId($table);
    return $result;
}

Bei der Registrierung wird der neuen Mediumdatei eine Identifikationsnummer zugewiesen.

Wir verbinden die registrierte Mediumdatei mit dem entsprechenden Produkt für das Default-Frontend:

function createGalleryValue($mediaId, $prodId)
{
    \/** @var MagentoFrameworkAppResourceConnection $this->resource *\/ 
    \/** @var MagentoFrameworkDBAdapterPdoMysql $conn *\/ 
    $conn = $this->resource->getConnection();
    $table = $this->resource->getTableName('catalog_product_entity_media_gallery_value');
    $bind = [
        'value_id' => $mediaId,
        \/\* verwenden Sie standardmäßig die Admin-Store-Ansicht *\/ 
        'store_id' => 0,
        'entity_id' => $prodId,
        'label' => null,
        \/\* wir haben nur ein Bild *\/ 
        'position' => 1,
        'disabled' => false
    ];
    $conn->insert($table, $bind);
}

Wir verbinden die registrierte Mediumdatei ohne Anbindung an einen bestimmten Store mit dem entsprechenden Produkt. Es ist unklar, wo genau diese Daten verwendet werden und warum die Daten der vorherigen Tabelle nicht verwendet werden können, aber diese Tabelle existiert und die Daten werden beim Hinzufügen eines Bildes zu einem Produkt hier gespeichert. Deshalb so.

function createGalleryValueToEntity($mediaId, $prodId)
{
    \/** @var MagentoFrameworkAppResourceConnection $this->resource *\/ 
    \/** @var MagentoFrameworkDBAdapterPdoMysql $conn *\/ 
    $conn = $this->resource->getConnection();
    $table = $this->resource->getTableName('catalog_product_entity_media_gallery_value_to_entity');
    $bind = [
        'value_id' => $mediaId,
        'entity_id' => $prodId
    ];
    $conn->insert($table, $bind);
}

catalog_product_entity_varchar

Mediumdateien können mit verschiedenen Rollen verwendet werden (der Code des entsprechenden Attributs ist in Klammern angegeben):

  • Basis (Image)
  • Kleinbild (small_image)
  • Miniaturansicht (thumbnail)
  • Musterbild (swatch_image)

Die Zuordnung von Rollen zu Mediumdateien erfolgt genau hier in catalog_product_entity_varchar. Der Code zur Zuordnung ist ähnlich wie der Code im Abschnitt „Basisattribute des Produkts«.

Nach dem Hinzufügen eines Bildes zum Produkt im Admin sieht es so aus:

Magento 2: Produktimport direkt in die Datenbank

Kategorien

Haupttabellen, die Daten zu Kategorien enthalten:

  • catalog_category_entity: Kategoriekatalog;
  • catalog_category_product: Verbindung von Produkten und Kategorien;
  • catalog_category_entity_*: Werte von EAV-Attributen;

Ursprünglich enthält die Kategoriedatenbank der leeren Magento-Anwendung 2 Kategorien (ich habe die Spaltennamen abgekürzt: crtcreated_at, updupdated_at):

entity_id|attribute_set_id|parent_id|crt|upd|path|position|level|children_count|
---------|----------------|---------|---|---|----|--------|-----|--------------|
        1|               3|        0|...|...|1   |       0|    0|             1|
        2|               3|        1|...|...|1/2 |       1|    1|             0|

Die Kategorie mit id=1 ist der Hauptknoten des gesamten Magento-Katalogs und ist weder im Admin noch im Frontend verfügbar. Die Kategorie mit id=2 (Default Category) ist die Stammkategorie für den Hauptshop der Hauptwebsite (Hauptwebsite Shop), die beim Bereitstellen der Anwendung erstellt wird (siehe Admin / Stores / Alle Stores). Außerdem ist die Stammkategorie des Shops im Frontend ebenfalls nicht verfügbar, nur ihre Unterkategorien.

Da das Thema dieses Artikels hauptsächlich den Datenimport von Produkten betrifft, werde ich keine direkte Datenbankeintragung bei der Erstellung von Kategorien verwenden, sondern die von Magento bereitgestellten Klassen (Modelle und Repositories) nutzen. Die direkte Datenbankeintragung wird nur verwendet, um das importierte Produkt mit der Kategorie zu verknüpfen (die Zuordnung der Kategorie erfolgt über ihren Namen, bei der Zuordnung wird die id der Kategorie abgerufen):

function create($prodId, $catId)
{
    /** @var MagentoFrameworkAppResourceConnection $this->resource */
    /** @var MagentoFrameworkDBAdapterPdoMysql $conn */
    $conn = $this->resource->getConnection();
    $table = $this->resource->getTableName('catalog_category_product');
    $bind = [
        'category_id' => $catId,
        'product_id' => $prodId,
    ];
    $conn->insert($table, $bind);
}

Nachdem die Verknüpfung des Produkts mit den Kategorien „Kategorie 1“ und „Kategorie 2“ hinzugefügt wurde, sieht es im Admin ungefähr so aus:

Magento 2: Produktimport direkt in die Datenbank

Zusätzliche Aktionen

Nach Abschluss des Datenimports müssen die folgenden zusätzlichen Schritte durchgeführt werden:

  • Datenindizierung: Aufruf in der Konsole ./bin/magento indexer:reindex;
  • Regenerierung von URLs für Produkte/Kategorien: Es kann die Erweiterung „elgentos/regenerate-catalog-urls«

Produkte im Admin nach Ausführung der zusätzlichen Schritte:

Magento 2: Produktimport direkt in die Datenbank

und im Frontend:

Magento 2: Produktimport direkt in die Datenbank

Zusammenfassung

Dasselbe Set an Produkten (10 Stück) wird, wie im vorherigen Artikel, mindestens um einen Faktor schneller importiert (1 Sekunde im Vergleich zu 10). Für eine genauere Geschwindigkeitsbewertung sind mehr Produkte erforderlich — mehrere Hundert, am besten Tausende. Dennoch kann selbst bei einem so kleinen Datenvolumen der Schluss gezogen werden, dass die Nutzung des von Magento bereitgestellten Werkzeugs (Modelle und Repositories) erheblich (ich betone — deutlich!) beschleunigen die Entwicklung der benötigten Funktionalität, senken jedoch gleichzeitig erheblich (ich betone — deutlich!) die Geschwindigkeit, mit der Daten in die Datenbank gelangen.

Am Ende stellte sich heraus, dass Wasser nass ist, was keine überraschende Erkenntnis ist. Dennoch habe ich jetzt den Code, um weiter zu experimentieren und möglicherweise interessantere Ergebnisse zu erzielen.

Quelle: habr.com

60GB SSD 8Gb DDR4