Magento 2: importazione dei prodotti direttamente nel database

In articolo precedente Ho descritto il processo di importazione dei prodotti in Magento 2 nel modo consueto — tramite modelli e repository. Questo metodo tradizionale presenta una velocità di elaborazione dei dati piuttosto bassa. Sul mio laptop, riuscivo a importare circa un prodotto al secondo. In questa continuazione, esploro un metodo alternativo per l'importazione dei prodotti — la scrittura diretta nel database, bypassando i meccanismi standard di Magento 2 (modelli, factory, repository). La sequenza di passaggi che garantisce l'importazione dei prodotti può essere adattata a qualsiasi linguaggio di programmazione capace di interagire con MySQL.

Avvertenza: In Magento è disponibile una funzionalità già pronta per l'importazione dei dati e probabilmente sarà sufficiente per le vostre esigenze. Tuttavia, se desiderate un controllo più completo sul processo di importazione, non limitato alla preparazione del file CSV, benvenuti sotto il tag.

Magento 2: importazione dei prodotti direttamente nel database

Il codice risultante dalla scrittura di entrambi gli articoli può essere visto nel modulo Magento «flancer32/mage2_ext_demo_import». Ecco alcune limitazioni seguite per semplificare il codice del modulo dimostrativo:

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

JSON per l'importazione di un singolo prodotto:

{
  "sku": "MVA20D-UBV-3",
  "name": "Tappo per sigillatura BA47-29 IEK",
  "desc": "Fornire accesso ai dispositivi ...",
  "desc_short": "Il tappo per sigillatura BA47-29 IEK è progettato per ...",
  "price": 5.00,
  "qty": 25,
  "categories": ["Categoria 1", "Categoria 2"],
  "image_path": "mva20d_ubv_3.png"
}

Panoramica delle principali fasi di importazione

  • Registrazione del prodotto stesso
  • Collegamento tra il prodotto e il sito web
  • Attributi di base del prodotto (EAV)
  • Dati di inventario (quantità del prodotto in magazzino)
  • Media (immagini)
  • Collegamento con le categorie del catalogo

Registrazione del prodotto

Le informazioni di base sul prodotto si trovano in catalog_product_entity:

CREA LA TABELLA `catalog_product_entity` (
  `entity_id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID Entità',
  `attribute_set_id` smallint(5) unsigned NOT NULL DEFAULT '0' COMMENT 'ID Set di Attributi',
  `type_id` varchar(32) NOT NULL DEFAULT 'simple' COMMENT 'ID Tipo',
  `sku` varchar(64) DEFAULT NULL COMMENT 'SKU',
  `has_options` smallint(6) NOT NULL DEFAULT '0' COMMENT 'Ha Opzioni',
  `required_options` smallint(5) unsigned NOT NULL DEFAULT '0' COMMENT 'Opzioni Richieste',
  `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Data di Creazione',
  `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'Data di Aggiornamento',
  PRIMARY KEY (`entity_id`),
  KEY `CATALOG_PRODUCT_ENTITY_ATTRIBUTE_SET_ID` (`attribute_set_id`),
  KEY `CATALOG_PRODUCT_ENTITY_SKU` (`sku`)
)

Informazioni minime necessarie per creare una registrazione nel registro dei prodotti:

  • attribute_set_id
  • sku

opzionale:

  • type_id — se non specificato, verrà utilizzato ‘simple’

Per la registrazione diretta nel database utilizzo l'adattatore DB di 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;
}

Dopo la registrazione del prodotto in catalog_product_entity diventa visibile nell'area amministrativa, nella griglia dei prodotti (Catalogo / Prodotti).

Magento 2: importazione dei prodotti direttamente nel database

Collegamento tra prodotto e sito web

Il collegamento del prodotto con il sito determina in quali negozi e su quali vetrine il prodotto sarà disponibile al pubblico.

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: importazione dei prodotti direttamente nel database

Attributi di base del prodotto

Un prodotto appena registrato non ha ancora né nome né descrizione. Tutto ciò può essere fatto tramite Attributi EAV. Ecco un elenco degli attributi di base del prodotto, necessari affinché il prodotto venga visualizzato correttamente al pubblico:

  • name
  • prezzo
  • descrizione
  • descrizione_breve
  • status
  • id_classe_imposta
  • chiave_url
  • visibilità

Un attributo aggiuntivo al prodotto può essere aggiunto in questo modo (i dettagli per ottenere l'identificatore e il tipo di attributo in base al suo codice sono omessi):

funzione pubblica create($prodId, $attrCode, $attrValue)
{
    $attrId = /* ottieni l'ID dell'attributo tramite il codice dell'attributo */
    $attrType = /* ottieni il tipo di attributo [datetime|decimal|int|text|varchar]) tramite il codice dell'attributo */
    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,
            /* inserisci tutti gli attributi nella vista predefinita del negozio con id=0 (admin) */
            'store_id' => 0,
            'value' => $attrValue
        ];
        $conn->insert($table, $bind);
    }
}

In base al codice dell'attributo, definiamo il suo id e il tipo di dati (datetime, decimal, -int, text, varchar), quindi scriviamo i dati nella tabella corrispondente per la vetrina amministrativa (store_id = 0).

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

Magento 2: importazione dei prodotti direttamente nel database

Dati di inventario

A partire dalla versione 2.3 in Magento, esistono due set di tabelle che forniscano la memorizzazione delle informazioni di inventario (quantità del prodotto):

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

È necessario aggiungere i dati di inventario in entrambe le strutture, poiché la nuova struttura non è ancora completamente indipendente dalla vecchia (sembra che per default il magazzino nella nuova struttura utilizza una tabella cataloginventory_stock_status come inventory_stock_1).

cataloginventory_

Durante il deployment di Magento 2.3 iniziamo con 2 record in store_website, che corrispondono a due siti — uno amministrativo e uno principale per i clienti:

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

Nella tabella cataloginventory_stock abbiamo solo un record:

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

Cioè, nella vecchia struttura abbiamo solo un «magazzino» (stock) e questo è associato al sito web amministrativo. L'aggiunta di nuovi sources/stocks in MSI (la nuova struttura) non porta alla creazione di nuovi record in cataloginventory_stock.

I dati di inventario sui prodotti nella vecchia struttura sono 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,
        /* utilizziamo solo un stock nella struttura 'cataloginventory' per impostazione predefinita */
        'stock_id' => 1,
        'qty' => $qty,
        'is_qty_decimal' => $isQtyDecimal,
        'is_in_stock' => $isInStock,
        /* lo stock predefinito è legato al sito web admin (vedi `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,
        /* utilizziamo solo un stock nella struttura 'cataloginventory' per impostazione predefinita */
        'stock_id' => 1, 
        'qty' => $qty,
        'stock_status' => MagentoCatalogInventoryApiDataStockStatusInterface::STATUS_IN_STOCK,
        /* lo stock predefinito è legato al sito web admin (vedi `cataloginventory_stock`) */
        'website_id' => 0 
    ];
    $conn->insert($table, $bind);
}

inventario_

Inizialmente, la nuova struttura per la memorizzazione dei dati di inventario contiene 1 «fonte» (sorgente_inventario):

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 un deposito fisico per i prodotti (la registrazione contiene coordinate fisiche e indirizzo postale). «Magazzino» rappresenta una fusione logica di più «fonti» (inventory_source_stock_link)

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

a livello del quale avviene il legame con il canale di vendita (inventory_stock_sales_channel)

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

Da quanto emerge dalla struttura dei dati, si presume l'esistenza di diversi tipi di canali di vendita, ma di default viene utilizzato solo il legame «stock«-«website» (il collegamento al sito web è gestito dal codice del sito web — base).

Uno «magazzino» può essere collegato a più «fonti», e uno «fonte» può essere legato a più «magazzini» (relazione «molti-a-molti»). Le eccezioni sono i «default» «fonte» e «magazzino». Non possono essere ricollegati ad altre entità (vincolo a livello di codice — si verifica l'errore «Can not save link related to Default Source or Default Stock«). Per maggiori dettagli sulla struttura MSI in Magento 2, puoi leggere l'articolo «Sistema di gestione del magazzino utilizzando CQRS e Event Sourcing. Progettazione«.

Utilizzerò la configurazione predefinita e aggiungerò tutte le informazioni relative all'inventario alla fonte default, che è coinvolta nel canale di vendita associato al sito web con codice base (corrisponde alla parte client 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, si ottiene la seguente immagine:

Magento 2: importazione dei prodotti direttamente nel database

Media

Quando si aggiungono manualmente le immagini al prodotto tramite il pannello di amministrazione, le informazioni corrispondenti vengono registrate nelle seguenti tabelle:

  • catalog_product_entity_media_gallery: registro media (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: associazione media solo ai prodotti (presumibilmente, contenuto multimediale predefinito per il prodotto);
  • catalog_product_entity_varchar: qui vengono memorizzati i ruoli in cui viene utilizzata l'immagine;

e le immagini stesse vengono salvate nella cartella ./pub/media/catalog/product/x/y/, dove x e y — la prima e la seconda lettera del nome del file immagine. Ad esempio, il file image.png deve essere salvato come ./pub/media/catalog/product/i/m/image.png, affinché la piattaforma possa utilizzarlo come immagine nella descrizione dei prodotti del catalogo.

Registriamo il media file in ./pub/media/catalog/product/ media file (il processo di caricamento del file in questo articolo non verrà trattato):

function createMediaGallery($imgPathPrefixed)
{
    $attrId = /* ottieni l'ID attributo per il codice attributo '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' o 'video' */
        'media_type' => 'image',
        'disabled' => false
    ];
    $conn->insert($table, $bind);
    $result = $conn->lastInsertId($table);
    return $result;
}

Quando un nuovo media file viene registrato, gli viene assegnato un identificatore.

Colleghiamo il media file 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 legarlo a nessuna vetrina. Non è chiaro dove vengano utilizzati questi dati e perché non si possa fare riferimento ai dati della tabella precedente, ma questa tabella esiste e i dati vengono scritti in essa quando si aggiunge un'immagine al prodotto. Quindi è così.

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

Il file multimediale può essere utilizzato con ruoli diversi (il codice dell'attributo corrispondente è indicato tra parentesi):

  • Base (image)
  • Immagine piccola (small_image)
  • Miniatura (thumbnail)
  • Immagine campione (swatch_image)

L'associazione dei ruoli ai file multimediali avviene proprio in catalog_product_entity_varchar. Il codice di associazione è simile a quello nella sezione «Attributi di base del prodotto«.

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

Magento 2: importazione dei prodotti direttamente nel database

Categorie

Le tabelle principali che contengono dati sulle categorie sono:

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

Inizialmente, in un'applicazione Magento vuota, nel registro delle categorie ci sono 2 categorie (ho abbreviato i nomi delle colonne: crtcreated_at, aggiornamentiupdated_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 ed è non accessibile né nel pannello di amministrazione né sul front-end. La categoria con id=2 (Default Category) è la categoria radice per il negozio principale del sito principale (Main Website Store), creata durante il deployment dell'applicazione (vedi. Admin / Stores / All Stores). Inoltre, la categoria radice del negozio non è accessibile sul front-end, solo le sue sottocategorie.

Poiché l'argomento di questo articolo è l'importazione dei dati sui prodotti, non userò la scrittura diretta nel database durante la creazione delle categorie, ma utilizzerò le classi fornite da Magento stesso (modelli e repository). La scrittura diretta nel database è utilizzata solo per collegare il prodotto importato alla categoria (il mapping della categoria avviene tramite il suo nome, estraendo l'ID della categoria durante il mapping):

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 il collegamento del prodotto con le categorie "Categoria 1" e "Categoria 2", i dettagli del prodotto nell'admin appaiono circa così:

Magento 2: importazione dei prodotti direttamente nel database

Azioni aggiuntive

Dopo aver completato l'importazione dei dati, è necessario eseguire le seguenti azioni aggiuntive:

  • indicizzazione dei dati: chiamata nella console ./bin/magento indexer:reindex;
  • rigenerazione degli URL per prodotti/categorie: è possibile utilizzare l'estensione "elgentos/regenerate-catalog-urls«

Prodotti nell'admin dopo aver eseguito le azioni aggiuntive:

Magento 2: importazione dei prodotti direttamente nel database

e sul fronte:

Magento 2: importazione dei prodotti direttamente nel database

Riepilogo

Lo stesso set di prodotti (10 pezzi), come nell'articolo precedente, viene importato almeno dieci volte più velocemente (1 secondo contro 10). Per una valutazione più precisa della velocità sono necessari un numero maggiore di prodotti — alcune centinaia, meglio ancora migliaia. Tuttavia, anche con una dimensione di input così ridotta, si può concludere che l'utilizzo degli strumenti forniti da Magento (modelli e repository) accelera notevolmente (sottolineo — notevolmente!) lo sviluppo delle funzionalità richieste, ma riduce drasticamente (sottolineo — notevolmente!) la velocità di inserimento dei dati nel database.

In definitiva, l'acqua si è rivelata bagnata e non è una grande rivelazione. Tuttavia, ora ho il codice con cui sperimentare ulteriormente e, forse, fare conclusioni più interessanti.

Fonte: habr.com

Acquista un hosting affidabile per siti web con protezione DDoS, VPS VDS server 🔥 Acquista un hosting affidabile per siti web con protezione DDoS, VPS VDS server | ProHoster