Magento 2: importació de productes directament a la base de dades

В article anterior Vaig descriure el procés d'importació de productes a Magento 2 de la manera habitual: mitjançant models i repositoris. El mètode habitual té una velocitat de processament de dades molt baixa. El meu ordinador portàtil produïa aproximadament un producte per segon. En aquesta continuació, considero una forma alternativa d'importar un producte: mitjançant l'entrada directa a la base de dades, obviant els mecanismes estàndard de Magento 2 (models, fàbriques, dipòsits). La seqüència de passos per importar productes es pot adaptar a qualsevol llenguatge de programació que pugui funcionar amb MySQL.

renúncia: Magento té una funcionalitat preparada per a importació de dades i, molt probablement, et serà suficient. Tanmateix, si necessiteu un control més complet sobre el procés d'importació, sense limitar-vos a preparar un fitxer CSV per al que teniu, benvingut a cat.

Magento 2: importació de productes directament a la base de dades

El codi resultant d'escriure ambdós articles es pot veure al mòdul Magento "flancer32/mage2_ext_demo_import". Aquí hi ha algunes restriccions que vaig seguir per simplificar el codi del mòdul de demostració:

  • Els productes només es creen, no s'actualitzen.
  • Un magatzem
  • Només s'importen els noms de categories, sense la seva estructura
  • Les estructures de dades compleixen la versió 2.3

JSON per importar un sol producte:

{
  "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"
}

Visió general de les principals etapes d'importació

  • registre del propi producte
  • connexió entre el producte i el lloc web
  • Atributs bàsics del producte (EAV)
  • dades d'inventari (quantitat de producte en estoc)
  • mitjans de comunicació (fotos)
  • connexió amb categories de catàleg

Registre del producte

La informació bàsica del producte es pot trobar a 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`)
)

La informació mínima necessària per crear una entrada al registre del producte és:

  • attribute_set_id
  • sku

addicionals:

  • type_id — si no ho especifiquem, s'utilitzarà "simple".

Per escriure directament a la base de dades, faig servir l'adaptador de base de dades del mateix 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;
}

Després de registrar el producte amb catalog_product_entity es fa visible al tauler d'administració, a la graella del producte (Catàleg/Productes).

Magento 2: importació de productes directament a la base de dades

Relació entre producte i lloc web

L'associació del producte amb el lloc determina en quines botigues i expositors el producte estarà disponible al davant.

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: importació de productes directament a la base de dades

Atributs bàsics del producte

El producte recentment registrat encara no té un nom ni una descripció. Tot això es fa a través Atributs EAV. Aquí hi ha una llista dels atributs bàsics del producte que es necessiten perquè el producte es mostri correctament a la part frontal:

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

S'afegeix un atribut independent a un producte com aquest (s'ometen els detalls per obtenir l'identificador i el tipus d'atribut del seu codi):

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

Utilitzant el codi d'atribut, determinem el seu identificador i el tipus de dades (datetime, decimal, int, text, varchar), després escriviu les dades de la finestra administrativa a la taula adequada (store_id = 0).

Després d'afegir els atributs anteriors al producte, obtindreu aquesta imatge al tauler d'administració:

Magento 2: importació de productes directament a la base de dades

Dades d'inventari

A partir de la versió 2.3 de Magento, hi ha dos conjunts paral·lels de taules que proporcionen emmagatzematge d'informació d'inventari (quantitat de producte):

  • cataloginventory_*: estructura antiga;
  • inventory_*: nova estructura (MSI - Multi Source Inventory);

Heu d'afegir dades d'inventari a ambdues estructures, perquè la nova estructura encara no és completament independent de l'antiga (és molt probable que per default magatzem a la nova estructura està implicada una taula cataloginventory_stock_status en qualitat inventory_stock_1).

cataloginventory_

En desplegar Magneto 2.3 inicialment tenim 2 entrades store_website, que correspon a dos llocs: administratiu i client principal:

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

Taula cataloginventory_stock només tenim una entrada:

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

És a dir, a la nostra antiga estructura només hi ha un "magatzem" (stock) i està enllaçat al web administratiu. Afegint-ne de nous a través del tauler d'administració sources/stocks a MSI (nova estructura) no dóna lloc a noves entrades a cataloginventory_stock.

Les dades d'inventari dels productes de l'estructura antiga es registren inicialment en taules:

  • 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,
        /* 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);
}

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,
        /* 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);
}

inventari_

Inicialment, la nova estructura per emmagatzemar dades d'inventari conté 1 "font'(inventory_source):

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

i un"magatzem'(inventory_stock):

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

«Font» representa l'emmagatzematge físic dels productes (el registre conté les coordenades físiques i l'adreça postal). "Magatzem"és una unió lògica de diverses "fonts" (inventory_source_stock_link)

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

al nivell en què es produeix la connexió amb el canal de venda (inventory_stock_sales_channel)

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

A jutjar per l'estructura de dades, s'assumeixen diversos tipus de canals de venda, però per defecte només la connexió "valors"-" "(l'enllaç al lloc web segueix el codi del lloc web - base).

Un"magatzem"pot estar vinculat a diversos"fonts"i un"font"-a diversos"magatzems"(relació de molts a molts). Les excepcions són per defecte "font"I"magatzem". No es tornen a enllaçar amb altres entitats (limitació a nivell de codi - l'error "No es pot desar l'enllaç relacionat amb la font predeterminada o l'estoc predeterminat"). Es poden trobar més detalls sobre l'estructura MSI de Magento 2 a l'article "Sistema de gestió de magatzems mitjançant CQRS i Event Sourcing. Disseny".

Faré servir la configuració predeterminada i afegiré tota la informació de l'inventari a la font default, que participa en el canal de venda associat al lloc web amb el codi base (correspon a la part davantera de la botiga - vegeu 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);
}

Després d'afegir dades d'inventari al producte al tauler d'administració, obtindreu aquesta imatge:

Magento 2: importació de productes directament a la base de dades

Mitjans de comunicació

Quan s'afegeix "manualment" una imatge a un producte a través del tauler d'administració, la informació rellevant s'anota a les taules següents:

  • catalog_product_entity_media_gallery: registre de mitjans (imatges i fitxers de vídeo);
  • catalog_product_entity_media_gallery_value: enllaçar mitjans amb productes i aparadors (localització);
  • catalog_product_entity_media_gallery_value_to_entity: enllaçar contingut multimèdia només amb productes (presumiblement contingut multimèdia predeterminat per al producte);
  • catalog_product_entity_varchar: aquí s'emmagatzemen els rols en què s'utilitza la imatge;

i les imatges es guarden al directori ./pub/media/catalog/product/x/y/On x и y — la primera i la segona lletra del nom del fitxer d'imatge. Per exemple, fitxer image.png s'ha de guardar com a ./pub/media/catalog/product/i/m/image.png, perquè la plataforma la pugui utilitzar com a imatge a l'hora de descriure productes del catàleg.

Registre publicat a ./pub/media/catalog/product/ fitxer multimèdia (el procés de col·locar el fitxer en si no es tracta en aquest article):

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

Quan es registra, a un nou fitxer multimèdia se li assigna un identificador.

Associem el fitxer multimèdia registrat amb el producte corresponent per a l'aparador predeterminat:

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

Associem el fitxer multimèdia registrat amb el producte corresponent sense estar lligats a cap aparador. No està clar on s'utilitzen exactament aquestes dades i per què és impossible accedir a les dades de la taula anterior, però aquesta taula existeix i les dades s'escriuen quan s'afegeix una imatge al producte. Així que això és tot.

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

Un fitxer multimèdia es pot utilitzar amb diferents rols (el codi d'atribut corresponent s'indica entre parèntesis):

  • Base (image)
  • Imatge petita (small_image)
  • Miniatura (thumbnail)
  • Imatge de mostra (swatch_image)

Enllaçar rols a un fitxer multimèdia és exactament el que passa a catalog_product_entity_varchar. El codi d'enllaç és similar al codi del "Atributs bàsics del producte".

Després d'afegir una imatge al producte al tauler d'administració, té aquest aspecte:

Magento 2: importació de productes directament a la base de dades

Категории

Taules principals que contenen dades per categories:

  • catalog_category_entity: registre de categories;
  • catalog_category_product: connexió entre productes i categories;
  • catalog_category_entity_*: valors d'atribut EAV;

Inicialment, en una aplicació Magento buida, el registre de categories conté 2 categories (he escurçat els noms de les columnes: 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|

La categoria amb id=1 és l'arrel de tot el catàleg de Magento i no està disponible ni al tauler d'administració ni a la pàgina principal. Categoria amb id=2 (Categoria per defecte) és la categoria arrel de la botiga principal del lloc principal (Botiga web principal) creat quan es desplega l'aplicació (vegeu. Administrador / Botigues / Totes les botigues). A més, la categoria arrel de la botiga en si no està disponible a la part davantera, només les seves subcategories.

Com que el tema d'aquest article encara és la importació de dades de productes, no utilitzaré l'entrada directa a la base de dades en crear categories, sinó que faré servir les classes proporcionades pel mateix Magento (models i repositoris). L'entrada directa a la base de dades només s'utilitza per associar el producte importat amb una categoria (la categoria coincideix amb el seu nom i l'identificador de la categoria es recupera durant la concordança):

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

Després d'afegir un enllaç de producte a les categories "Categoria 1" i "Categoria 2", els detalls del producte al tauler d'administració tenen un aspecte semblant a això:

Magento 2: importació de productes directament a la base de dades

Accions addicionals

Un cop finalitzada la importació de dades, heu de completar els passos addicionals següents:

  • indexació de dades: trucada a la consola ./bin/magento indexer:reindex;
  • regenerant URL per a productes/categories: podeu utilitzar l'extensió “elgents/regenerate-catalog-urls«

Productes al tauler d'administració després de realitzar accions addicionals:

Magento 2: importació de productes directament a la base de dades

i al davant:

Magento 2: importació de productes directament a la base de dades

Resum

El mateix conjunt de productes (10 peces) que a l'article anterior s'importa almenys un ordre de magnitud més ràpid (1 segon enfront de 10). Per estimar amb més precisió la velocitat, necessiteu un nombre més gran de productes: diversos centenars, o millor encara milers. No obstant això, fins i tot amb una mida tan petita de dades d'entrada, podem concloure que l'ús de les eines proporcionades per Magento (models i repositoris) és important (subratllo: molt!) accelerar el desenvolupament de la funcionalitat requerida, però al mateix temps significativament (subratllo: molt!) reduir la velocitat a la qual les dades entren a la base de dades.

Com a resultat, l'aigua va resultar humida i això no és una revelació. Tanmateix, ara tinc el codi per jugar i potser arribar a algunes conclusions més interessants.

Font: www.habr.com