Magento 2: importa i prodotti direttamente nel database

В articolo precedente Ho descritto il processo di importazione dei prodotti in Magento 2 nel solito modo, attraverso modelli e repository. Il metodo abituale ha una velocità di elaborazione dei dati molto bassa. Il mio laptop produceva circa un prodotto al secondo. In questa continuazione, considererò un modo alternativo per importare un prodotto: mediante l'immissione diretta nel database, aggirando i meccanismi standard di Magento 2 (modelli, fabbriche, repository). La sequenza dei passaggi per importare i prodotti può essere adattata a qualsiasi linguaggio di programmazione che possa funzionare con MySQL.

Negazione di responsabilità: Magento ha funzionalità già pronte per importazione dei dati e, molto probabilmente, ti basterà. Tuttavia, se hai bisogno di un controllo più completo sul processo di importazione, non limitato alla preparazione di un file CSV per ciò che hai, benvenuto in cat.

Magento 2: importa i prodotti direttamente nel database

Il codice risultante dalla scrittura di entrambi gli articoli è visualizzabile nel modulo Magento"flancer32/mage2_ext_demo_import". Ecco alcune restrizioni che ho seguito per semplificare il codice del modulo demo:

  • I prodotti vengono solo creati, non aggiornati.
  • Un magazzino
  • Vengono importati solo i nomi delle categorie, senza la relativa struttura
  • Le strutture dati sono conformi alla versione 2.3

JSON per importare un singolo prodotto:

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

Panoramica delle principali fasi dell'importazione

  • registrazione del prodotto stesso
  • connessione tra prodotto e sito web
  • attributi base del prodotto (EAV)
  • dati di inventario (quantità di prodotto in stock)
  • media (immagini)
  • connessione con le categorie del catalogo

Registrazione del prodotto

Le informazioni di base sul prodotto sono disponibili 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`)
)

Le informazioni minime richieste per creare una voce nel registro del prodotto sono:

  • attribute_set_id
  • sku

aggiuntivo:

  • type_id — se non lo specifichiamo, verrà utilizzato "semplice".

Per scrivere direttamente nel database, utilizzo l'adattatore DB di Magento stesso:

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

Dopo aver registrato il prodotto con catalog_product_entity diventa visibile nel pannello di amministrazione, nella griglia del prodotto (Catalogo/Prodotti).

Magento 2: importa i prodotti direttamente nel database

Rapporto tra prodotto e sito web

L'associazione del prodotto al sito determina in quali negozi ed esposizioni il prodotto sarà disponibile in primo piano.

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: importa i prodotti direttamente nel database

Attributi di base del prodotto

Il prodotto appena registrato non ha ancora un nome o una descrizione. Tutto questo viene fatto attraverso Attributi EAV. Ecco un elenco degli attributi di base del prodotto necessari affinché il prodotto venga visualizzato correttamente sulla parte anteriore:

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

Un attributo separato viene aggiunto a un prodotto come questo (i dettagli su come ottenere l'identificatore e il tipo di attributo dal suo codice vengono omessi):

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

Utilizzando il codice dell'attributo, determiniamo il suo ID e il tipo di dati (datetime, decimal, int, text, varchar), quindi scrivere i dati per la finestra amministrativa nell'apposita tabella (store_id = 0).

Dopo aver aggiunto gli attributi di cui sopra al prodotto, ottieni questa immagine nel pannello di amministrazione:

Magento 2: importa i prodotti direttamente nel database

Dati di inventario

A partire dalla versione 2.3 in Magento, ci sono due serie parallele di tabelle che forniscono l'archiviazione delle informazioni di inventario (quantità del prodotto):

  • cataloginventory_*: vecchia struttura;
  • inventory_*: nuova struttura (MSI - Multi Source Inventory);

È necessario aggiungere i dati di inventario a entrambe le strutture, perché la nuova struttura non è ancora del tutto indipendente da quella vecchia (è molto probabile che per default magazzino nella nuova struttura è coinvolto un tavolo cataloginventory_stock_status come inventory_stock_1).

catalogoinventario_

Quando distribuiamo Magneto 2.3 inizialmente abbiamo 2 voci store_website, che corrisponde a due siti: amministrativo e client principale:

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

Tavolo cataloginventory_stock abbiamo solo una voce:

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

Cioè nella nostra vecchia struttura esiste un solo “magazzino” (stock) ed è collegato al sito amministrativo. Aggiunta di nuovi tramite il pannello di amministrazione sources/stocks nel MSI (nuova struttura) non comporta nuove iscrizioni cataloginventory_stock.

I dati di inventario sui prodotti nella vecchia struttura vengono inizialmente registrati nelle tabelle:

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

inventario_

Inizialmente, la nuova struttura per l'archiviazione dei dati di inventario contiene 1 "fonte"(inventory_source):

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

e uno "magazzino"(inventory_stock):

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

«Fonte» rappresenta l'immagazzinamento fisico dei prodotti (il record contiene le coordinate fisiche e l'indirizzo postale). "magazzino"è un'unione logica di più "fonti" (inventory_source_stock_link)

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

al livello in cui avviene il collegamento al canale di vendita (inventory_stock_sales_channel)

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

A giudicare dalla struttura dei dati si presuppongono vari tipi di canali di vendita, ma per impostazione predefinita solo la connessione”azione"-"sito web"(il collegamento al sito segue il codice del sito - base).

Uno "magazzino"può essere collegato a diversi"fonti"e uno "fonte" - a diversi "magazzini"(relazione molti-a-molti). Le eccezioni sono predefinite"fonte"E"magazzino". Non vengono ricollegati ad altre entità (limitazione a livello di codice - l'errore “Impossibile salvare il collegamento relativo all'origine predefinita o allo stock predefinito"). Maggiori dettagli sulla struttura MSI in Magento 2 possono essere trovati nell’articolo “Sistema di gestione del magazzino che utilizza CQRS ed Event Sourcing. Progetto«.

Utilizzerò la configurazione predefinita e aggiungerò tutte le informazioni sull'inventario all'origine default, che è coinvolto nel canale di vendita associato al sito web con il codice base (corrisponde al front-end del negozio - vedi 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);
}

Dopo aver aggiunto i dati di inventario al prodotto nel pannello di amministrazione, ottieni questa immagine:

Magento 2: importa i prodotti direttamente nel database

media

Quando si aggiunge “manualmente” un'immagine a un prodotto tramite il pannello di amministrazione, le informazioni rilevanti vengono scritte nelle seguenti tabelle:

  • catalog_product_entity_media_gallery: registro multimediale (immagini e file video);
  • catalog_product_entity_media_gallery_value: collegamento dei media ai prodotti e alle vetrine (localizzazione);
  • catalog_product_entity_media_gallery_value_to_entity: collegamento dei media solo ai prodotti (presumibilmente contenuto multimediale predefinito per il prodotto);
  • catalog_product_entity_varchar: i ruoli in cui viene utilizzata l'immagine vengono memorizzati qui;

e le immagini stesse vengono salvate nella directory ./pub/media/catalog/product/x/y/Dove x и y — la prima e la seconda lettera del nome del file immagine. Ad esempio, file image.png dovrebbe essere salvato come ./pub/media/catalog/product/i/m/image.png, in modo che la piattaforma possa utilizzarlo come immagine nella descrizione dei prodotti del catalogo.

Registro pubblicato in ./pub/media/catalog/product/ file multimediale (il processo di posizionamento del file stesso non è discusso in questo articolo):

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

Una volta registrato, a un nuovo file multimediale viene assegnato un identificatore.

Associamo il file multimediale registrato al prodotto corrispondente per la vetrina predefinita:

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

Associamo il file multimediale registrato al prodotto corrispondente senza essere vincolati ad alcuna vetrina. Non è chiaro dove vengano utilizzati esattamente questi dati e perché sia ​​impossibile accedere ai dati della tabella precedente, ma questa tabella esiste e i dati vengono scritti in essa quando viene aggiunta un'immagine al prodotto. Quindi è tutto.

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 file multimediale può essere utilizzato con diversi ruoli (tra parentesi è indicato il codice dell'attributo corrispondente):

  • Basi (image)
  • Piccola immagine (small_image)
  • Miniatura (thumbnail)
  • Immagine del campione (swatch_image)

Collegare i ruoli a un file multimediale è esattamente ciò che accade in catalog_product_entity_varchar. Il codice vincolante è simile al codice nel "Attributi di base del prodotto«.

Dopo aver aggiunto un'immagine al prodotto nel pannello di amministrazione, appare così:

Magento 2: importa i prodotti direttamente nel database

categoria

Principali tabelle contenenti i dati per categoria:

  • catalog_category_entity: registro delle categorie;
  • catalog_category_product: connessione tra prodotti e categorie;
  • catalog_category_entity_*: valori degli attributi EAV;

Inizialmente, in un'applicazione Magento vuota, il registro delle categorie contiene 2 categorie (ho abbreviato i nomi delle colonne: 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 con id=1 è la radice dell'intero catalogo Magento e non è disponibile né nel pannello di amministrazione né nella prima pagina. Categoria con ID=2 (Predefinito Categoria) è la categoria principale per il negozio principale del sito principale (Negozio principale del sito web) creato al momento della distribuzione dell'applicazione (vedere. Amministrazione/Negozi/Tutti i negozi). Inoltre, in primo piano non è disponibile nemmeno la categoria principale del negozio stesso, ma solo le sue sottocategorie.

Poiché l'argomento di questo articolo è ancora l'importazione dei dati sui prodotti, non utilizzerò l'immissione diretta nel database durante la creazione delle categorie, ma utilizzerò le classi fornite da Magento stesso (modelli e repository). L'inserimento diretto nel database viene utilizzato solo per associare il prodotto importato ad una categoria (la categoria viene abbinata tramite il suo nome e l'id della categoria viene recuperato durante l'abbinamento):

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

Dopo aver aggiunto un collegamento al prodotto alle categorie “Categoria 1” e “Categoria 2”, i dettagli del prodotto nel pannello di amministrazione saranno simili a questi:

Magento 2: importa i prodotti direttamente nel database

Azioni aggiuntive

Una volta completata l'importazione dei dati, è necessario completare i seguenti passaggi aggiuntivi:

  • indicizzazione dei dati: chiamata in console ./bin/magento indexer:reindex;
  • rigenerazione URL per prodotti/categorie: puoi utilizzare l'estensione “elgentos/regenerate-catalog-urls«

Prodotti nel pannello di amministrazione dopo aver eseguito azioni aggiuntive:

Magento 2: importa i prodotti direttamente nel database

e nella parte anteriore:

Magento 2: importa i prodotti direttamente nel database

Riassunto

Lo stesso set di prodotti (10 pezzi) dell'articolo precedente viene importato almeno un ordine di grandezza più velocemente (1 secondo contro 10). Per stimare con maggiore precisione la velocità, è necessario un numero maggiore di prodotti: diverse centinaia, o meglio ancora migliaia. Tuttavia, anche con una dimensione così ridotta dei dati di input, possiamo concludere che l'utilizzo degli strumenti forniti da Magento (modelli e repository) è significativo (sottolineo - molto!) accelerano lo sviluppo delle funzionalità richieste, ma allo stesso tempo in modo significativo (sottolineo - molto!) riducono la velocità con cui i dati entrano nel database.

Di conseguenza, l'acqua si è rivelata bagnata e questa non è una rivelazione. Tuttavia, ora ho il codice con cui giocare e forse arrivare a conclusioni più interessanti.

Fonte: habr.com