Magento 2: import produse direct în baza de date

В anterioară articol Am descris procesul de import de produse în Magento 2 în modul obișnuit - prin modele și depozite. Metoda obișnuită are o viteză foarte mică de procesare a datelor. Laptopul meu producea aproximativ un produs pe secundă. În această continuare, consider o modalitate alternativă de a importa un produs - prin intrare directă în baza de date, ocolind mecanismele standard Magento 2 (modele, fabrici, depozite). Secvența de pași pentru importul produselor poate fi adaptată oricărui limbaj de programare care poate funcționa cu MySQL.

Declinare a responsabilităţii: Magento are o funcționalitate gata făcută pentru import de date și, cel mai probabil, îți va fi suficient. Cu toate acestea, dacă aveți nevoie de un control mai complet asupra procesului de import, fără a se limita la pregătirea unui fișier CSV pentru ceea ce aveți, bine ați venit la cat.

Magento 2: import produse direct în baza de date

Codul rezultat din scrierea ambelor articole poate fi vizualizat in modulul Magento "flancer32/mage2_ext_demo_import". Iată câteva restricții pe care le-am urmat pentru a simplifica codul modulului demonstrativ:

  • Produsele sunt doar create, nu actualizate.
  • Un singur depozit
  • Sunt importate doar numele de categorii, fără structura lor
  • Structurile de date sunt conforme cu versiunea 2.3

JSON pentru importarea unui singur produs:

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

Prezentare generală a principalelor etape ale importului

  • înregistrarea produsului în sine
  • conexiune între produs și site-ul web
  • atributele de bază ale produsului (EAV)
  • date de inventar (cantitatea de produs în stoc)
  • media (imagini)
  • legătura cu categoriile de catalog

Inregistrarea produsului

Informațiile de bază despre produs pot fi găsite în 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`)
)

Informațiile minime necesare pentru a crea o intrare în registrul produsului sunt:

  • attribute_set_id
  • sku

adiţional:

  • type_id — dacă nu o specificăm, atunci se va folosi „simple”.

Pentru a scrie direct în baza de date, folosesc adaptorul DB al Magento însuși:

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

După înregistrarea produsului cu catalog_product_entity devine vizibil în panoul de administrare, în grila de produse (Catalog/Produse).

Magento 2: import produse direct în baza de date

Relația dintre produs și site-ul web

Asocierea produsului cu site-ul determină în ce magazine și expoziții va fi disponibil produsul în față.

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: import produse direct în baza de date

Atributele de bază ale produsului

Produsul nou înregistrat nu are încă un nume sau descriere. Toate acestea se fac prin Atributele EAV. Iată o listă cu atributele de bază ale produsului care sunt necesare pentru ca produsul să fie afișat corect pe față:

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

Un atribut separat este adăugat unui produs ca acesta (detaliile obținerii identificatorului și tipului atributului din codul său sunt omise):

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

Folosind codul de atribut, determinăm id-ul și tipul de date (datetime, decimal, int, text, varchar), apoi scrieți datele pentru fereastra administrativă în tabelul corespunzător (store_id = 0).

După adăugarea atributelor de mai sus la produs, obțineți această imagine în panoul de administrare:

Magento 2: import produse direct în baza de date

Date de inventar

Începând cu versiunea 2.3 în Magento, există două seturi paralele de tabele care oferă stocarea informațiilor despre inventar (cantitatea de produs):

  • cataloginventory_*: structura veche;
  • inventory_*: structura noua (MSI - Multi Source Inventory);

Trebuie să adăugați date de inventar la ambele structuri, deoarece noua structură nu este încă complet independentă de cea veche (este foarte probabil ca pt default depozit în noua structură este implicată o masă cataloginventory_stock_status la fel de inventory_stock_1).

cataloginventory_

Când implementăm Magneto 2.3, avem inițial 2 intrări store_website, care corespunde a două site-uri - administrativ ș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|

Masa cataloginventory_stock avem o singură intrare:

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

Adică, în vechea noastră structură există un singur „depozit” (stock) și este legat de site-ul administrativ. Adăugarea unor noi prin panoul de administrare sources/stocks în MSI (nouă structură) nu are ca rezultat noi intrări în cataloginventory_stock.

Datele de inventar despre produsele din vechea structură sunt inițial înregistrate în tabele:

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

inventar_

Inițial, noua structură pentru stocarea datelor de inventar conține 1 "sursă'(inventory_source):

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

și unul "depozit'(inventory_stock):

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

«Sursă» reprezintă depozitul fizic pentru produse (înregistrarea conține coordonatele fizice și adresa de corespondență). "Depozit„este o uniune logică a mai multor „surse” (inventory_source_stock_link)

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

la nivelul la care are loc conexiunea la canalul de vânzare (inventory_stock_sales_channel)

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

Judecând după structura datelor, se presupun diverse tipuri de canale de vânzare, dar implicit doar conexiunea „stoc"-" "(linkul către site urmează codul site-ului - base).

Unu "depozit"poate fi legat de mai multe"surse"și unul "sursă"-la mai multe"depozite„(relație multi-la-mulți). Excepțiile sunt implicite "sursă"Și"depozit". Ele nu sunt reconectate cu alte entități (limitare la nivel de cod - eroarea „Nu se poate salva linkul legat de Sursa implicită sau Stocul implicit"). Mai multe detalii despre structura MSI din Magento 2 pot fi găsite în articolul „Sistem de management al depozitelor folosind CQRS și Event Sourcing. Proiecta“.

Voi folosi configurația implicită și voi adăuga toate informațiile de inventar la sursă default, care este implicat în canalul de vânzare asociat site-ului cu codul base (corespunde cu partea din față a magazinului - vezi 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);
}

După ce adăugați date de inventar la produs în panoul de administrare, obțineți această imagine:

Magento 2: import produse direct în baza de date

Mass-media

Când adăugați „manual” o imagine la un produs prin panoul de administrare, informațiile relevante sunt notate în următoarele tabele:

  • catalog_product_entity_media_gallery: registru media (imagini si fisiere video);
  • catalog_product_entity_media_gallery_value: legarea mass-media la produse și vitrine (localizare);
  • catalog_product_entity_media_gallery_value_to_entity: conectarea media la produse numai (presumabil conținut media implicit pentru produs);
  • catalog_product_entity_varchar: Rolurile în care este folosită imaginea sunt stocate aici;

iar imaginile în sine sunt salvate în director ./pub/media/catalog/product/x/y/Unde x и y — prima și a doua literă ale numelui fișierului imagine. De exemplu, fișier image.png ar trebui salvat ca ./pub/media/catalog/product/i/m/image.png, astfel încât platforma să o poată folosi ca imagine atunci când descrie produsele din catalog.

Înregistrare postat în ./pub/media/catalog/product/ fișier media (procesul de plasare a fișierului în sine nu este discutat în acest articol):

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

Când este înregistrat, unui nou fișier media i se atribuie un identificator.

Asociem fișierul media înregistrat cu produsul corespunzător pentru vitrina implicită:

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

Asociem fișierul media înregistrat cu produsul corespunzător fără a fi legați de vreo vitrină. Nu este clar unde se folosesc exact aceste date și de ce este imposibil să accesezi datele din tabelul anterior, dar acest tabel există și datele sunt scrise în el când se adaugă o poză la produs. Deci asta este.

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 fișier media poate fi utilizat cu diferite roluri (codul de atribut corespunzător este indicat în paranteze):

  • Baza (image)
  • Imagine mică (small_image)
  • Miniatură (thumbnail)
  • Eșantion imagine (swatch_image)

Conectarea rolurilor la un fișier media este exact ceea ce se întâmplă în catalog_product_entity_varchar. Codul obligatoriu este similar cu codul din „Atributele de bază ale produsului“.

După adăugarea unei imagini la produs în panoul de administrare, arată astfel:

Magento 2: import produse direct în baza de date

Categorie

Tabelele principale care conțin date pe categorii:

  • catalog_category_entity: registrul categoriilor;
  • catalog_category_product: legatura intre produse si categorii;
  • catalog_category_entity_*: valorile atributelor EAV;

Inițial, într-o aplicație Magento goală, registrul de categorii conține 2 categorii (am scurtat numele coloanelor: 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|

Categoria cu id=1 este rădăcina întregului catalog Magento și nu este disponibilă nici în panoul de administrare, nici pe prima pagină. Categorie cu id=2 (Implicit Categorie) este categoria rădăcină pentru magazinul principal al site-ului principal (Magazinul principal de site-uri web) creat atunci când aplicația este implementată (vezi. Administrator / Magazine / Toate magazinele). Mai mult decât atât, categoria rădăcină a magazinului în sine nu este disponibilă nici în față, ci doar subcategoriile sale.

Deoarece subiectul acestui articol este încă importul de date despre produse, nu voi folosi intrarea directă în baza de date atunci când creez categorii, ci voi folosi clasele oferite de Magento însuși (modele și depozitele). Intrarea directă în baza de date este utilizată numai pentru a asocia produsul importat cu o categorie (categoria este potrivită după numele acesteia, iar id-ul categoriei este preluat în timpul potrivirii):

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

După adăugarea unui link de produs la categoriile „Categoria 1” și „Categoria 2”, detaliile produsului din panoul de administrare arată cam așa:

Magento 2: import produse direct în baza de date

Acțiuni suplimentare

Odată ce importul datelor este complet, trebuie să parcurgeți următorii pași suplimentari:

  • indexarea datelor: apel în consolă ./bin/magento indexer:reindex;
  • regenerarea adreselor URL pentru produse/categorii: puteți folosi extensia „elgentos/regenerate-catalog-urls«

Produse din panoul de administrare după efectuarea unor acțiuni suplimentare:

Magento 2: import produse direct în baza de date

si in fata:

Magento 2: import produse direct în baza de date

Rezumat

Același set de produse (10 bucăți) ca în articolul precedent este importat cu cel puțin un ordin de mărime mai rapid (1 secundă față de 10). Pentru a estima mai precis viteza, aveți nevoie de un număr mai mare de produse - câteva sute, sau mai bine zis mii. Cu toate acestea, chiar și cu o dimensiune atât de mică a datelor de intrare, putem concluziona că utilizarea instrumentelor furnizate de Magento (modele și depozite) este semnificativă (subliniez - mult!) accelerează dezvoltarea funcționalității necesare, dar în același timp semnificativ (subliniez - mult!) reduceți viteza cu care datele intră în baza de date.

Drept urmare, apa s-a dovedit a fi umedă și aceasta nu este o revelație. Cu toate acestea, acum am codul cu care să mă joc și poate ajung la niște concluzii mai interesante.

Sursa: www.habr.com