Magento 2: importeer producten rechtstreeks in de database

В vorige artikel Ik beschreef het proces van het importeren van producten in Magento 2 op de gebruikelijke manier - via modellen en repositories. De gebruikelijke methode heeft een zeer lage gegevensverwerkingssnelheid. Mijn laptop produceerde ongeveer één product per seconde. In dit vervolg overweeg ik een alternatieve manier om een ​​product te importeren - door directe toegang tot de database, waarbij de standaard Magento 2-mechanismen (modellen, fabrieken, repositories) worden omzeild. De volgorde van stappen om producten te importeren kan worden aangepast aan elke programmeertaal die met MySQL kan werken.

Disclaimer: Magento heeft kant-en-klare functionaliteit voor gegevens importeren en hoogstwaarschijnlijk zal het genoeg voor je zijn. Als u echter meer volledige controle over het importproces nodig heeft, en niet beperkt bent tot het voorbereiden van een CSV-bestand voor wat u heeft, dan bent u van harte welkom bij cat.

Magento 2: importeer producten rechtstreeks in de database

De code die voortkomt uit het schrijven van beide artikelen kunt u bekijken in de Magento module"lancer32/mage2_ext_demo_import". Hier zijn enkele beperkingen die ik heb gevolgd om de demomodulecode te vereenvoudigen:

  • Producten worden alleen gemaakt, niet bijgewerkt.
  • Eén magazijn
  • Alleen categorienamen worden geïmporteerd, zonder hun structuur
  • Datastructuren voldoen aan versie 2.3

JSON voor het importeren van één product:

{
  "sku": "MVA20D-UBV-3",
  "name": "Заглушка для пломбировки ВА47-29 IEK",
  "desc": "Обеспечение доступа к устройствам ...",
  "desc_short": "Заглушка для пломбировки ВА47-29 IEK предназначена для ...",
  "price": 5.00,
  "qty": 25,
  "categories": ["Категория 1", "Категория 2"],
  "image_path": "mva20d_ubv_3.png"
}

Overzicht van de belangrijkste fasen van import

  • registratie van het product zelf
  • verbinding tussen product en website
  • basisproductkenmerken (EAV)
  • voorraadgegevens (hoeveelheid product op voorraad)
  • media (foto's)
  • verbinding met cataloguscategorieën

Product registratie

Basisproductinformatie vindt u 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`)
)

De minimale informatie die vereist is om een ​​vermelding in het productregister te maken, is:

  • attribute_set_id
  • sku

aanvullend:

  • type_id — als we het niet specificeren, wordt 'eenvoudig' gebruikt

Om rechtstreeks naar de database te schrijven gebruik ik de DB-adapter van Magento zelf:

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 registratie van het product bij catalog_product_entity het wordt zichtbaar in het beheerderspaneel, in het productraster (Catalogus/Producten).

Magento 2: importeer producten rechtstreeks in de database

Relatie tussen product en website

De associatie van het product met de site bepaalt in welke winkels en displays het product aan de voorkant verkrijgbaar zal zijn.

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: importeer producten rechtstreeks in de database

Basisproductkenmerken

Het nieuw geregistreerde product heeft nog geen naam of beschrijving. Dit alles gebeurt via EAV-attributen. Hier is een lijst met basisproductkenmerken die nodig zijn om ervoor te zorgen dat het product correct op de voorkant wordt weergegeven:

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

Aan een product als dit wordt een afzonderlijk attribuut toegevoegd (de details voor het verkrijgen van de identificatie en het type van het attribuut uit 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);
    }
}

Met behulp van de attribuutcode bepalen we de ID en het gegevenstype (datetime, decimal, int, text, varchar), schrijf vervolgens de gegevens voor het administratieve venster in de juiste tabel (store_id = 0).

Nadat u de bovenstaande kenmerken aan het product heeft toegevoegd, krijgt u deze afbeelding in het beheerderspaneel:

Magento 2: importeer producten rechtstreeks in de database

Voorraadgegevens

Vanaf versie 2.3 in Magento zijn er twee parallelle sets tabellen die de opslag van voorraadinformatie (producthoeveelheid) mogelijk maken:

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

U moet inventarisgegevens aan beide structuren toevoegen, omdat de nieuwe structuur is nog niet volledig onafhankelijk van de oude (het is zeer waarschijnlijk dat default magazijn in de nieuwe structuur is een tafel betrokken cataloginventory_stock_status als inventory_stock_1).

catalogusinventaris_

Bij het inzetten van Magneto 2.3 hebben we in eerste instantie 2 vermeldingen in store_website, wat overeenkomt met twee sites: administratief en hoofdclient:

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

Tafel cataloginventory_stock we hebben maar één inzending:

stock_id|website_id|stock_name|
--------|----------|----------|
       1|         0|Default   |

Dat wil zeggen, in onze oude structuur is er maar één ‘magazijn’ (stock) en is gekoppeld aan de administratieve website. Nieuwe toevoegen via het beheerderspaneel sources/stocks in MSI (nieuwe structuur) resulteert niet in nieuwe vermeldingen in cataloginventory_stock.

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

  • cataloginventory_stock_item
  • cataloginventory_stock_status

catalogusinventaris_voorraad_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,
        /* we use one only stock in 'cataloginventory' structure by default */
        'stock_id' => 1,
        'qty' => $qty,
        'is_qty_decimal' => $isQtyDecimal,
        'is_in_stock' => $isInStock,
        /* default stock is bound to admin website (see `cataloginventory_stock`) */
        'website_id' => 0
    ];
    $conn->insert($table, $bind);
}

catalogusinventaris_voorraad_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,
        /* we use one only stock in 'cataloginventory' structure by default */
        'stock_id' => 1, 
        'qty' => $qty,
        'stock_status' => MagentoCatalogInventoryApiDataStockStatusInterface::STATUS_IN_STOCK,
        /* default stock is bound to admin website (see `cataloginventory_stock`) */
        'website_id' => 0 
    ];
    $conn->insert($table, $bind);
}

inventaris_

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

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

en een "magazijn"(inventory_stock):

stock_id|name         |
--------|-------------|
       1|Default Stock|

«Bron» vertegenwoordigt de fysieke opslag voor producten (het record bevat fysieke coördinaten en postadres). "Magazijn"is een logische vereniging van verschillende" bronnen "(inventory_source_stock_link)

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

op het niveau waarop de aansluiting op het verkoopkanaal plaatsvindt (inventory_stock_sales_channel)

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

Afgaande op de datastructuur wordt uitgegaan van verschillende soorten verkoopkanalen, maar standaard wordt alleen de verbinding “voorraad"-"van de"(de link naar de website volgt de websitecode - base).

Een "magazijn"kan aan meerdere gekoppeld worden"bronnen"en een "bron" - aan verschillende "magazijnen"(veel-op-veel-relatie). De uitzonderingen zijn standaard "bron"En"magazijn". Ze zijn niet opnieuw gekoppeld aan andere entiteiten (beperking op codeniveau - de fout “Kan link met betrekking tot standaardbron of standaardvoorraad niet opslaan"). Meer details over de MSI-structuur in Magento 2 vindt u in het artikel “Magazijnbeheersysteem met behulp van CQRS en Event Sourcing. Ontwerp".

Ik gebruik de standaardconfiguratie en voeg alle inventarisinformatie toe aan de bron default, die betrokken is bij het verkoopkanaal dat is gekoppeld aan de website met de code base (komt overeen met de voorkant 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);
}

Nadat u voorraadgegevens aan het product heeft toegevoegd in het beheerdersdashboard, krijgt u deze afbeelding:

Magento 2: importeer producten rechtstreeks in de database

media

Wanneer u via het admin-paneel “handmatig” een afbeelding aan een product toevoegt, wordt de relevante informatie in de volgende tabellen opgeschreven:

  • catalog_product_entity_media_gallery: mediaregister (afbeeldingen en videobestanden);
  • catalog_product_entity_media_gallery_value: media koppelen aan producten en showcases (lokalisatie);
  • catalog_product_entity_media_gallery_value_to_entity: media alleen aan producten koppelen (vermoedelijk standaardmedia-inhoud voor het product);
  • catalog_product_entity_varchar: De rollen waarin de afbeelding wordt gebruikt, worden hier opgeslagen;

en de afbeeldingen zelf worden in de map opgeslagen ./pub/media/catalog/product/x/y/Waar x и y — de eerste en tweede letter van de naam van het beeldbestand. Bestand bijvoorbeeld image.png moet worden opgeslagen als ./pub/media/catalog/product/i/m/image.png, zodat het platform het als afbeelding kan gebruiken bij het beschrijven van producten uit de catalogus.

Register geplaatst in ./pub/media/catalog/product/ mediabestand (het proces van het plaatsen van het bestand zelf wordt in dit artikel niet besproken):

function createMediaGallery($imgPathPrefixed)
{
    $attrId = /* get attribute ID by attribute code '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' or 'video' */
        'media_type' => 'image',
        'disabled' => false
    ];
    $conn->insert($table, $bind);
    $result = $conn->lastInsertId($table);
    return $result;
}

Bij registratie krijgt een nieuw mediabestand een identificatie toegewezen.

We koppelen het geregistreerde mediabestand aan het overeenkomstige product voor de standaard storefront:

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,
        /* use admin store view by default */
        'store_id' => 0,
        'entity_id' => $prodId,
        'label' => null,
        /* we have one only image */
        'position' => 1,
        'disabled' => false
    ];
    $conn->insert($table, $bind);
}

Wij koppelen het geregistreerde mediabestand aan het overeenkomstige product, zonder dat wij aan een etalage gebonden zijn. Het is niet duidelijk waar deze gegevens precies worden gebruikt en waarom het onmogelijk is om toegang te krijgen tot de gegevens uit de vorige tabel, maar deze tabel bestaat en de gegevens worden ernaartoe geschreven wanneer een afbeelding aan het product wordt toegevoegd. Dus dat is het.

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

catalogus_product_entiteit_varchar

Een mediabestand kan met verschillende rollen worden gebruikt (de bijbehorende attribuutcode wordt tussen haakjes aangegeven):

  • Basis (image)
  • Kleine afbeelding (small_image)
  • Miniatuur (thumbnail)
  • Voorbeeldafbeelding (swatch_image)

Het koppelen van rollen aan een mediabestand is precies wat er gebeurt in catalog_product_entity_varchar. De bindende code is vergelijkbaar met de code in de "Basisproductkenmerken".

Na het toevoegen van een afbeelding aan het product in het admin-paneel ziet het er als volgt uit:

Magento 2: importeer producten rechtstreeks in de database

categorie

Hoofdtabellen met gegevens per categorie:

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

In eerste instantie bevat het categorieregister in een lege Magento-applicatie 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|

De categorie met id=1 is de root van de gehele Magento-catalogus en is niet beschikbaar in het beheerdersdashboard of op de voorpagina. Categorie met id=2 (standaard Categorie) is de hoofdcategorie voor de hoofdwinkel van de hoofdsite (Hoofdwebsitewinkel) gemaakt wanneer de applicatie wordt geïmplementeerd (zie. Beheerder / Winkels / Alle winkels). Bovendien is de hoofdcategorie van de winkel zelf ook niet aan de voorkant beschikbaar, alleen de subcategorieën.

Omdat het onderwerp van dit artikel nog steeds het importeren van gegevens over producten is, zal ik bij het maken van categorieën geen directe invoer in de database gebruiken, maar de klassen gebruiken die door Magento zelf worden aangeboden (modellen en repositories). Directe invoer in de database wordt alleen gebruikt om het geïmporteerde product aan een categorie te koppelen (de categorie wordt gekoppeld aan de naam en de categorie-ID wordt opgehaald tijdens het matchen):

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 een productlink aan de categorieën “Categorie 1” en “Categorie 2”, zien de productdetails in het beheerderspaneel er ongeveer zo uit:

Magento 2: importeer producten rechtstreeks in de database

Aanvullende acties

Zodra de gegevensimport is voltooid, moet u de volgende aanvullende stappen uitvoeren:

  • gegevensindexering: roep de console in ./bin/magento indexer:reindex;
  • URL’s voor producten/categorieën opnieuw genereren: u kunt de extensie “elgentos/regenerate-catalog-urls«

Producten in het beheerdersdashboard na het uitvoeren van aanvullende acties:

Magento 2: importeer producten rechtstreeks in de database

en aan de voorkant:

Magento 2: importeer producten rechtstreeks in de database

Beknopt

Dezelfde set producten (10 stuks) als in het vorige artikel wordt minimaal een orde van grootte sneller geïmporteerd (1 seconde versus 10). Om de snelheid nauwkeuriger te schatten, hebt u een groter aantal producten nodig: enkele honderden, of beter nog duizenden. Maar zelfs met zo’n kleine omvang van de invoergegevens kunnen we concluderen dat het gebruik van de tools van Magento (modellen en repositories) aanzienlijk is (ik benadruk: veel!) de ontwikkeling van de vereiste functionaliteit versnellen, maar tegelijkertijd aanzienlijk (ik benadruk - veel!) verminder de snelheid waarmee gegevens in de database terechtkomen.

Hierdoor bleek het water nat te zijn en dit is geen openbaring. Nu heb ik echter de code om mee te spelen en kom ik misschien tot enkele interessantere conclusies.

Bron: www.habr.com