Magento 2: producten direct in de database importeren

In vorig artikel Ik heb het proces van het importeren van producten in Magento 2 op de gebruikelijke manier beschreven — via modellen en repositories. De gebruikelijke manier onderscheidt zich door een vrij lage gegevensverwerkingssnelheid. Op mijn laptop kwam het ongeveer op één product per seconde uit. In dit vervolg bespreek ik een alternatieve manier om een product te importeren — door rechtstreeks in de database te schrijven, langs de standaardmechanismen van Magento 2 (modellen, fabrieken, repositories). De volgorde van stappen die de import van producten mogelijk maken, kan worden aangepast aan elke programmeertaal die met MySQL kan werken.

Disclaimer: Magento heeft kant-en-klare functionaliteit voor gegevensimport en waarschijnlijk heeft u voldoende aan deze functionaliteit. Maar als u meer controle wilt over het importproces, dat niet beperkt is tot het voorbereiden van een CSV-bestand voor wat er is — welkom onder de kat.

Magento 2: producten direct in de database importeren

De code die is voortgekomen uit het schrijven van beide artikelen kan worden bekeken in de Magento-module "flancer32/mage2_ext_demo_import", Hier zijn enkele beperkingen waaraan ik me heb gehouden om de demo-module te vereenvoudigen:

  • Producten worden alleen aangemaakt, niet bijgewerkt.
  • Één magazijn
  • Alleen de namen van de categorieën worden geïmporteerd, zonder hun structuur
  • De gegevensstructuren komen overeen met versie 2.3

JSON voor de import van een enkel product:

{
  "sku": "MVA20D-UBV-3",
  "name": "Afsluitdop voor verzegeling VA47-29 IEK",
  "desc": "Verstrekking van toegang tot apparaten ...",
  "desc_short": "De afsluitdop voor verzegeling VA47-29 IEK is bedoeld voor ...",
  "price": 5.00,
  "qty": 25,
  "categories": ["Categorie 1", "Categorie 2"],
  "image_path": "mva20d_ubv_3.png"
}

Overzicht van de belangrijkste stappen van de import

  • registratie van het product zelf
  • verbinding tussen het product en de website
  • basisattributen van het product (EAV)
  • voorraadgegevens (aantal producten op voorraad)
  • media (afbeeldingen)
  • verbinding met de productcategorieën

Productregistratie

Basisinformatie over het product bevindt zich 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 'Has Options',
  `required_options` smallint(5) unsigned NOT NULL DEFAULT '0' COMMENT 'Required Options',
  `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Creation Time',
  `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'Update Time',
  PRIMARY KEY (`entity_id`),
  KEY `CATALOG_PRODUCT_ENTITY_ATTRIBUTE_SET_ID` (`attribute_set_id`),
  KEY `CATALOG_PRODUCT_ENTITY_SKU` (`sku`)
)

Minimale informatie die nodig is voor het aanmaken van een vermelding in het productregister:

  • attribute_set_id
  • sku

extra:

  • type_id — als we dit niet opgeven, wordt 'simple' gebruikt

Voor directe invoer in de database gebruik ik de DB-adapter van Magento:

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;
}

Na de registratie van het product in catalog_product_entity wordt het zichtbaar in de admin, in de productgrid (Catalog / Products).

Magento 2: producten direct in de database importeren

Koppeling van product en website

De koppeling van het product met de website bepaalt in welke winkels en op welke etalages het product beschikbaar zal zijn aan de voorkant.

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: producten direct in de database importeren

Basisattributen van het product

Een net geregistreerd product heeft nog geen naam of beschrijving. Dit alles wordt gedaan via EAV-attributen. Hier is een lijst van basisattributen van het product die nodig zijn om het product correct weer te geven aan de voorkant:

  • naam
  • price
  • description
  • short_description
  • status
  • tax_class_id
  • url_key
  • visibility

Een apart attribuut aan het product wordt als volgt toegevoegd (details over het verkrijgen van de ID en het type attribuut op basis van de code zijn weggelaten):

public function create($prodId, $attrCode, $attrValue)
{
    $attrId = /* get attribute ID by attribute code */
    $attrType = /* get attribute type [datetime|decimal|int|text|varchar] by attribute code */
    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,
            /* put all attributes to default store view with id=0 (admin) */
            'store_id' => 0,
            'value' => $attrValue
        ];
        $conn->insert($table, $bind);
    }
}

Aan de hand van de code van het attribuut bepalen we de id en het datatype (datetime, decimaal, int, text, varchar), en schrijven vervolgens de gegevens voor de admin-etalage in de bijbehorende tabel (store_id = 0).

Na het toevoegen van de bovengenoemde attributen aan het product ziet het er als volgt uit in de admin:

Magento 2: producten direct in de database importeren

Voorraadgegevens

Vanaf versie 2.3 bestaat er in Magento tegelijkertijd twee sets tabellen die zorgen voor de opslag van informatie over de voorraad (aantal producten):

  • cataloginventory_*: oude structuur;
  • inventory_*: nieuwe structuur (MSI — Multi Source Inventory);

Inventarisgegevens moeten aan beide structuren worden toegevoegd, omdat de nieuwe structuur nog niet volledig onafhankelijk is van de oude (het lijkt erop dat voor default het magazijn in de nieuwe structuur een tabel wordt gebruikt cataloginventory_stock_status als inventory_stock_1).

cataloginventory_

Bij de uitrol van Magneto 2.3 hebben we aanvankelijk 2 records in store_website, wat overeenkomt met twee websites — een administratieve en de belangrijkste klantwebsite:

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

In de tabel cataloginventory_stock hebben we slechts één record:

stock_id|website_id|stock_name|
--------|----------|----------|
       1|         0|Standaard   |

Dat wil zeggen, in de oude structuur hebben we slechts één 'magazijn' (stock) en deze is gekoppeld aan de administratieve website. Toevoegingen via de admin van nieuwe sources/stocks in MSI (de nieuwe structuur) leiden niet tot nieuwe records in cataloginventory_stock.

Inventarisgegevens over producten in de oude structuur worden in eerste instantie vastgelegd in de tabellen:

  • cataloginventory_stock_item
  • cataloginventory_stock_status

cataloginventory_stock_item

function createOldItem($prodId, $qty)
{
    $isQtyDecimal = (((int)$qty) != $qty);
    $isInStock = ($qty > 0);
    rief @var MagentoFrameworkAppResourceConnection $this->resource *
    rief @var MagentoFrameworkDBAdapterPdoMysql $conn *
    $conn = $this->resource->getConnection();
    $table = $this->resource->getTableName('cataloginventory_stock_item');
    $bind = [
        'product_id' => $prodId,
        rief we use one only stock in 'cataloginventory' structure by default *
        'stock_id' => 1,
        'qty' => $qty,
        'is_qty_decimal' => $isQtyDecimal,
        'is_in_stock' => $isInStock,
        rief default stock is bound to admin website (see `cataloginventory_stock`) *
        'website_id' => 0
    ];
    $conn->insert($table, $bind);
}

cataloginventory_stock_status

function createOldStatus($prodId, $qty)
{
    $isInStock = ($qty > 0);
    rief @var MagentoFrameworkAppResourceConnection $this->resource *
    rief @var MagentoFrameworkDBAdapterPdoMysql $conn *
    $conn = $this->resource->getConnection();
    $table = $this->resource->getTableName('cataloginventory_stock_status');
    $bind = [
        'product_id' => $prodId,
        rief we use one only stock in 'cataloginventory' structure by default *
        'stock_id' => 1, 
        'qty' => $qty,
        'stock_status' => MagentoCatalogInventoryApiDataStockStatusInterface::STATUS_IN_STOCK,
        rief default stock is bound to admin website (see `cataloginventory_stock`) *
        'website_id' => 0 
    ];
    $conn->insert($table, $bind);
}

inventory_

Aanvankelijk bevat de nieuwe structuur voor het opslaan van inventarisgegevens 1 'bron» (inventory_source):

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

en één 'magazijn» (inventory_stock):

stock_id|name         |
--------|-------------|
       1|Standaardvoorraad|

«Bron» vertegenwoordigt een fysieke opslag voor producten (de record bevat fysieke coördinaten en een postadres). «Opslag» vertegenwoordigt een logische combinatie van verschillende «bronnen» (inventory_source_stock_link)

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

op welk niveau de koppeling naar het verkoopkanaal plaatsvindt (inventory_stock_sales_channel)

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

Gezien de datastructuur worden verschillende typen verkoopkanalen verondersteld, maar standaard wordt alleen de relatie «stock«-«website» gebruikt (de link naar de website gaat via de code van de website — base).

Één «magazijn» kan aan meerdere «bronnen«, en één «bron» kan aan meerdere «opslagplaatsen» (de relatie «veel-naar-veel»). Uitzonderingen zijn de standaard «bron» en «magazijn«. Deze worden niet opnieuw gekoppeld aan andere entiteiten (beperking op het niveau van de code — er komt een foutmelding «Kan de link die is gekoppeld aan de Standaard Bron of Standaard Opslag niet opslaan«). Meer gedetailleerde informatie over de structuur van MSI in Magento 2 kan worden gelezen in het artikel «Warehouse Management System met behulp van CQRS en Event Sourcing. Ontwerp«.

Ik ga de standaardconfiguratie gebruiken en alle voorraadgegevens toevoegen aan de bron default, die betrokken is bij het verkoopkanaal dat verbonden is aan de website met de code base (komt overeen met de klantzijde van de winkel — zie. store_website):

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

Na het toevoegen van de voorraadgegevens aan het product in de beheerdersomgeving ziet het er als volgt uit:

Magento 2: producten direct in de database importeren

Media

Bij 'handmatig' toevoegen van afbeeldingen aan het product via de beheerdersomgeving wordt de bijbehorende informatie opgeslagen in de volgende tabellen:

  • catalog_product_entity_media_gallery: media-register (afbeeldingen en videobestanden);
  • catalog_product_entity_media_gallery_value: koppeling van media aan producten en vitrines (lokalisatie);
  • catalog_product_entity_media_gallery_value_to_entity: koppeling van media alleen aan producten (vermoedelijk, standaard mediacontent voor het product);
  • catalog_product_entity_varchar: hier worden de rollen opgeslagen waarin de afbeelding wordt gebruikt;

en de afbeeldingen zelf worden opgeslagen in de catalogus .\/pub\/media\/catalog\/product\/x\/y\/, waar x en y — de eerste en tweede letters van de naam van het afbeeldingsbestand. Bijvoorbeeld, bestand image.png moet worden opgeslagen als ./pub/media/catalog/product/i/m/image.png, zodat het platform deze kan gebruiken als afbeelding bij het beschrijven van producten uit de catalogus.

Geregistreerd in . /pub/media/catalog/product/ media-bestand (het proces van het uploaden van het bestand wordt in dit artikel niet behandeld):

function createMediaGallery($imgPathPrefixed)
{
    $attrId = /* haal het attribuut-ID op via de attribuutcode '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' of 'video' */
        'media_type' => 'image',
        'disabled' => false
    ];
    $conn->insert($table, $bind);
    $result = $conn->lastInsertId($table);
    return $result;
}

Bij de registratie krijgt het nieuwe media-bestand een identificatienummer toegewezen.

We koppelen het geregistreerde media-bestand aan het overeenkomstige product voor de default-winkel:

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,
        /* gebruik de admin winkelweergave standaard */
        'store_id' => 0,
        'entity_id' => $prodId,
        'label' => null,
        /* we hebben maar één afbeelding */
        'position' => 1,
        'disabled' => false
    ];
    $conn->insert($table, $bind);
}

We koppelen het geregistreerde media-bestand aan het overeenkomstige product zonder een specifieke winkelbinding. Het is onduidelijk waar deze gegevens precies worden gebruikt en waarom we niet kunnen verwijzen naar de gegevens in de vorige tabel, maar deze tabel bestaat en de gegevens worden erin opgenomen bij het toevoegen van een afbeelding aan het product. Daarom zo.

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

Een media-bestand kan verschillende rollen hebben (de bijbehorende attribuutcode is tussen haakjes weergegeven):

  • Basis (afbeelding)
  • Kleine afbeelding (small_image)
  • Thumbnail (thumbnail)
  • Swatch afbeelding (swatch_image)

De toewijzing van rollen aan een media-bestand gebeurt in catalog_product_entity_varchar. De code voor de toewijzing is vergelijkbaar met de code in de sectie "Basisattributen van het product«.

Na het toevoegen van een afbeelding aan het product in de backend ziet het er zo uit:

Magento 2: producten direct in de database importeren

Categorieën

De belangrijkste tabellen waarin de gegevens over de categorieën zijn opgeslagen:

  • catalog_category_entity: register van categorieën;
  • catalog_category_product: verbinding tussen producten en categorieën;
  • catalog_category_entity_*: waarden van EAV-attributen;

In het standaard Magento-toepassing bevat het categorie-register 2 categorieën (ik heb de kolomnamen ingekort: crt — created_at, upd — updated_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|

Categorie met id=1 is de wortel van de hele Magento-catalogus en is niet toegankelijk in de admin of aan de voorkant. Categorie met id=2 (Standaard Categorie) is de wortelcategorie voor de hoofdwinkel van de hoofdwebsite (Hoofdwebsite Winkel), die wordt aangemaakt bij het implementeren van de applicatie (zie Admin / Stores / All Stores). Bovendien is de wortelcategorie van de winkel ook niet toegankelijk aan de voorkant, alleen de subcategorieën zijn dat.

Aangezien het onderwerp van dit artikel toch de import van productgegevens is, zal ik geen directe invoer in de database gebruiken bij het aanmaken van categorieën, maar gebruik maken van de klassen die door Magento zelf worden geleverd (modellen en repositories). Directe invoer in de database wordt alleen gebruikt voor het koppelen van het geïmporteerde product aan de categorie (de koppeling van de categorie gebeurt op basis van de naam, bij de koppeling wordt het id van de categorie opgehaald):

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);
}

Na het toevoegen van de koppeling van het product met de categorieën 'Categorie 1' en 'Categorie 2' zien de productdetails in de admin er ongeveer zo uit:

Magento 2: producten direct in de database importeren

Extra acties

Na het voltooien van de gegevensimport moeten de volgende extra acties worden uitgevoerd:

  • gegevensindexering: aanroep in de console ./bin/magento indexer:reindex;
  • regeneratie van URL's voor producten/categorieën: je kunt de extensie 'elgentos/regenerate-catalog-urls«

Producten in de admin na het uitvoeren van de extra acties:

Magento 2: producten direct in de database importeren

en aan de voorkant:

Magento 2: producten direct in de database importeren

Samenvatting

Dezelfde set van producten (10 stuks), zoals in het vorige artikel, wordt nu minstens een orde van grootte sneller geïmporteerd (1 seconde in plaats van 10). Voor een nauwkeurigere beoordeling van de snelheid zijn nog meer producten nodig — een paar honderd, en het liefst duizenden. Desondanks kan zelfs bij zo'n kleine inputgrootte geconcludeerd worden dat het gebruik van de tools die door Magento worden geleverd (modellen en repositories) aanzienlijk (ik benadruk — significant!) versnellen de ontwikkeling van de vereiste functionaliteit, maar verminderen tegelijkertijd aanzienlijk (ik benadruk — significant!) de snelheid waarmee gegevens in de database komen.

Uiteindelijk bleek het water nat te zijn en dat is zeker geen onthulling. Desondanks heb ik nu de code om verder te experimenteren en misschien interessantere conclusies te trekken.

Bron: habr.com

Koop betrouwbare webhosting met bescherming tegen DDoS, VPS VDS servers 🔥 Koop betrouwbare webhosting met bescherming tegen DDoS, VPS VDS servers | ProHoster