Magento 2: import produktů přímo do databáze

В předchozí článek Proces importu produktů do Magento 2 jsem popsal běžným způsobem – přes modely a repozitáře. Obvyklá metoda má velmi nízkou rychlost zpracování dat. Můj notebook produkoval asi jeden produkt za sekundu. V tomto pokračování zvažuji alternativní způsob importu produktu – přímým vstupem do databáze, obcházení standardních mechanismů Magento 2 (modely, továrny, úložiště). Posloupnost kroků k importu produktů lze přizpůsobit jakémukoli programovacímu jazyku, který umí pracovat s MySQL.

Odmítnutí odpovědnosti: Magento má připravené funkce pro import dat a s největší pravděpodobností vám to bude stačit. Pokud však potřebujete úplnější kontrolu nad procesem importu, neomezující se na přípravu souboru CSV pro to, co máte, vítejte na cat.

Magento 2: import produktů přímo do databáze

Kód vzniklý napsáním obou článků si můžete prohlédnout v modulu Magento "flancer32/mage2_ext_demo_import". Zde jsou některá omezení, kterými jsem se řídil, abych zjednodušil kód demo modulu:

  • Produkty jsou pouze vytvářeny, nikoli aktualizovány.
  • Jeden sklad
  • Importují se pouze názvy kategorií bez jejich struktury
  • Datové struktury odpovídají verzi 2.3

JSON pro import jednoho produktu:

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

Přehled hlavních fází importu

  • registraci samotného produktu
  • propojení mezi produktem a webem
  • základní atributy produktu (EAV)
  • skladové údaje (množství produktu na skladě)
  • média (obrázky)
  • propojení s katalogovými kategoriemi

Registrace výrobku

Základní informace o produktu naleznete v 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`)
)

Minimální informace potřebné k vytvoření záznamu v registru produktů jsou:

  • attribute_set_id
  • sku

další:

  • type_id — pokud jej neuvedeme, použije se „jednoduchý“.

Pro přímý zápis do databáze používám DB adaptér samotného Magenta:

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

Po registraci produktu u catalog_product_entity bude viditelný na panelu správce, v mřížce produktu (Katalog/Produkty).

Magento 2: import produktů přímo do databáze

Vztah mezi produktem a webem

Přidružení produktu k webu určuje, ve kterých obchodech a výstavách bude produkt dostupný na přední straně.

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 produktů přímo do databáze

Základní vlastnosti produktu

Nově registrovaný produkt ještě nemá název ani popis. To vše se provádí prostřednictvím EAV atributy. Zde je seznam základních atributů produktu, které jsou potřeba pro správné zobrazení produktu na přední straně:

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

K produktu, jako je tento, je přidán samostatný atribut (podrobnosti o získání identifikátoru a typu atributu z jeho kódu jsou vynechány):

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

Pomocí kódu atributu určíme jeho id a datový typ (datetime, decimal, int, text, varchar), poté zapište data pro administrativní okno do příslušné tabulky (store_id = 0).

Po přidání výše uvedených atributů k produktu získáte tento obrázek v admin panelu:

Magento 2: import produktů přímo do databáze

Údaje o zásobách

Počínaje verzí 2.3 v Magento existují dvě paralelní sady tabulek, které poskytují ukládání informací o zásobách (množství produktu):

  • cataloginventory_*: stará struktura;
  • inventory_*: nová struktura (MSI - Multi Source Inventory);

Do obou struktur musíte přidat inventární data, protože nová struktura ještě není zcela nezávislá na staré (je velmi pravděpodobné, že pro default sklad v nové struktuře je zahrnuta tabulka cataloginventory_stock_status jak inventory_stock_1).

katalogový inventář_

Při nasazení Magneta 2.3 máme zpočátku 2 položky store_website, což odpovídá dvěma webům – administrativnímu a hlavnímu klientovi:

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

Stůl cataloginventory_stock máme pouze jeden záznam:

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

To znamená, že v naší staré struktuře je pouze jeden „sklad“ (stock) a je propojen s administrativním webem. Přidávání nových prostřednictvím panelu administrátora sources/stocks v MSI (nová struktura) nevede k novým záznamům v cataloginventory_stock.

Údaje o zásobách o produktech ve staré struktuře jsou zpočátku zaznamenány v tabulkách:

  • cataloginventory_stock_item
  • cataloginventory_stock_status

katalogová_skladová_položka

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

katalogový_stav_skladů

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

inventář_

Zpočátku nová struktura pro ukládání dat zásob obsahuje 1 "zdroj"(inventory_source):

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

a jeden"sklad"(inventory_stock):

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

«Zdroj» představuje fyzické úložiště pro produkty (záznam obsahuje fyzické souřadnice a poštovní adresu). "sklad"je logické spojení několika "zdrojů" (inventory_source_stock_link)

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

na úrovni, na které dochází k připojení k prodejnímu kanálu (inventory_stock_sales_channel)

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

Soudě podle struktury dat se předpokládají různé typy prodejních kanálů, ale standardně pouze spojení „stock„-“webových stránkách "(odkaz na webovou stránku následuje za kódem webu - base).

Jeden "sklad"lze propojit s několika"ke zdrojům"a jeden"zdroj"- na několik"sklady"(vztah mnoho k mnoha). Výjimky jsou výchozí "zdroj"A"sklad". Nejsou znovu propojeny s jinými entitami (omezení na úrovni kódu – chyba “Nelze uložit odkaz související s výchozím zdrojem nebo výchozím skladem"). Více podrobností o struktuře MSI v Magento 2 naleznete v článku “Systém řízení skladu využívající CQRS a Event Sourcing. Design".

Použiji výchozí konfiguraci a do zdroje přidám všechny informace o inventáři default, která je zapojena do prodejního kanálu spojeného s webovou stránkou s kódem base (odpovídá přední části prodejny - viz 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);
}

Po přidání inventárních dat k produktu na panelu administrátora získáte tento obrázek:

Magento 2: import produktů přímo do databáze

média

Při „ručním“ přidávání obrázku k produktu prostřednictvím panelu administrátora jsou příslušné informace zapsány do následujících tabulek:

  • catalog_product_entity_media_gallery: registr médií (obrázky a video soubory);
  • catalog_product_entity_media_gallery_value: propojení médií s produkty a vitrínami (lokalizace);
  • catalog_product_entity_media_gallery_value_to_entity: propojení médií pouze s produkty (pravděpodobně výchozí mediální obsah pro produkt);
  • catalog_product_entity_varchar: Zde jsou uloženy role, ve kterých je obrázek použit;

a samotné obrázky se uloží do adresáře ./pub/media/catalog/product/x/y/Kde x и y — první a druhé písmeno názvu souboru obrázku. Například soubor image.png by měl být uložen jako ./pub/media/catalog/product/i/m/image.png, aby jej platforma mohla použít jako obrázek při popisu produktů z katalogu.

Registrace zveřejněna v ./pub/media/catalog/product/ mediální soubor (proces umístění samotného souboru není v tomto článku popsán):

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

Při registraci je novému mediálnímu souboru přiřazen identifikátor.

Registrovaný mediální soubor přidružíme k odpovídajícímu produktu pro výchozí výlohu:

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

Registrovaný mediální soubor spojujeme s odpovídajícím produktem, aniž bychom byli vázáni na jakoukoli výlohu. Není jasné, kde přesně jsou tato data použita a proč není možné získat přístup k datům z předchozí tabulky, ale tato tabulka existuje a data se do ní zapisují, když je k produktu přidán obrázek. Tak to je vše.

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

katalog_produkt_entity_varchar

Mediální soubor lze použít s různými rolemi (odpovídající kód atributu je uveden v závorce):

  • Základna (image)
  • Malý obrázek (small_image)
  • Miniatura (thumbnail)
  • Vzorek obrázku (swatch_image)

Propojení rolí s mediálním souborem je přesně to, co se děje v catalog_product_entity_varchar. Závazný kód je podobný kódu v "Základní vlastnosti produktu".

Po přidání obrázku k produktu v panelu administrátora to vypadá takto:

Magento 2: import produktů přímo do databáze

kategorie

Hlavní tabulky obsahující data podle kategorií:

  • catalog_category_entity: rejstřík kategorií;
  • catalog_category_product: spojení mezi produkty a kategoriemi;
  • catalog_category_entity_*: hodnoty atributů EAV;

Zpočátku v prázdné aplikaci Magento obsahuje registr kategorií 2 kategorie (zkrátil jsem názvy sloupců: 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|

Kategorie s id=1 je kořenem celého katalogu Magento a není dostupná ani v admin panelu, ani na titulní stránce. Kategorie s id=2 (Výchozí kategorie) je kořenová kategorie pro hlavní obchod hlavního webu (Hlavní webový obchod) vytvořené při nasazení aplikace (viz. Správce / Obchody / Všechny obchody). Navíc kořenová kategorie samotného obchodu také není k dispozici na přední straně, pouze její podkategorie.

Vzhledem k tomu, že tématem tohoto článku je stále import dat o produktech, nepoužiji při vytváření kategorií přímý vstup do databáze, ale použiji třídy, které poskytuje samotné Magento (modely a repozitáře). Přímý vstup do databáze se používá pouze k přidružení importovaného produktu ke kategorii (kategorie se shoduje s jejím názvem a ID kategorie se získá při porovnávání):

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

Po přidání odkazu na produkt do kategorií „Kategorie 1“ a „Kategorie 2“ vypadají detaily produktu v administračním panelu asi takto:

Magento 2: import produktů přímo do databáze

Další akce

Po dokončení importu dat je třeba provést následující dodatečné kroky:

  • indexování dat: volání v konzole ./bin/magento indexer:reindex;
  • regenerace URL pro produkty/kategorie: můžete použít rozšíření “elgentos/regenerate-catalog-urls«

Produkty na panelu administrátora po provedení dalších akcí:

Magento 2: import produktů přímo do databáze

a vepředu:

Magento 2: import produktů přímo do databáze

Shrnutí

Stejná sada produktů (10 kusů) jako v předchozím článku se dováží minimálně o řád rychleji (1 sekunda versus 10). K přesnějšímu odhadu rychlosti potřebujete větší počet produktů – několik stovek, nebo ještě lépe tisíců. I při tak malé velikosti vstupních dat však můžeme usoudit, že využití nástrojů poskytovaných Magentem (modely a repozitáře) je významné (zdůrazňuji - hodně!) urychlit vývoj požadované funkcionality, ale zároveň výrazně (zdůrazňuji - hodně!) snížit rychlost, kterou se data dostávají do databáze.

V důsledku toho se ukázalo, že voda byla mokrá a není to žádné zjevení. Nyní však mám kód, se kterým si mohu pohrát a možná dospět k zajímavějším závěrům.

Zdroj: www.habr.com