Magento 2: importera produkter direkt till databasen

В tidigare artikel Jag beskrev processen att importera produkter till Magento 2 på vanligt sätt - genom modeller och arkiv. Den vanliga metoden har en mycket låg databehandlingshastighet. Min bärbara dator producerade ungefär en produkt per sekund. I den här fortsättningen överväger jag ett alternativt sätt att importera en produkt - genom att gå direkt in i databasen, förbi de vanliga Magento 2-mekanismerna (modeller, fabriker, repositories). Sekvensen av steg för att importera produkter kan anpassas till alla programmeringsspråk som kan fungera med MySQL.

Villkor: Magento har färdig funktionalitet för dataimport och troligen kommer det att räcka för dig. Men om du behöver mer fullständig kontroll över importprocessen, inte begränsat till att förbereda en CSV-fil för det du har, välkommen till cat.

Magento 2: importera produkter direkt till databasen

Koden som är resultatet av att skriva båda artiklarna kan ses i Magento-modulen "flancer32/mage2_ext_demo_import". Här är några begränsningar jag följde för att förenkla demomodulkoden:

  • Produkter skapas endast, inte uppdateras.
  • Ett lager
  • Endast kategorinamn importeras, utan deras struktur
  • Datastrukturer följer version 2.3

JSON för att importera en enskild produkt:

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

Översikt över importens huvudstadier

  • registrering av själva produkten
  • koppling mellan produkt och webbplats
  • grundläggande produktattribut (EAV)
  • lagerdata (mängd produkt i lager)
  • media (bilder)
  • koppling till katalogkategorier

Produktregistrering

Grundläggande produktinformation finns i 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`)
)

Den minsta information som krävs för att skapa en post i produktregistret är:

  • attribute_set_id
  • sku

ytterligare:

  • type_id — om vi inte anger det kommer 'enkel' att användas

För att skriva direkt till databasen använder jag Magentos DB-adapter:

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

Efter att ha registrerat produkten hos catalog_product_entity det blir synligt i adminpanelen, i produktrutnätet (Katalog/Produkter).

Magento 2: importera produkter direkt till databasen

Relation mellan produkt och hemsida

Produktens koppling till sajten avgör i vilka butiker och visningar produkten kommer att finnas på framsidan.

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: importera produkter direkt till databasen

Grundläggande produktegenskaper

Den nyregistrerade produkten har ännu inget namn eller beskrivning. Allt detta görs igenom EAV-attribut. Här är en lista över grundläggande produktattribut som behövs för att produkten ska visas korrekt på framsidan:

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

Ett separat attribut läggs till en produkt som denna (detaljerna för att erhålla identifieraren och typen av attributet från dess kod utelämnas):

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

Med hjälp av attributkoden bestämmer vi dess id och datatyp (datetime, decimal, int, text, varchar), skriv sedan data för det administrativa fönstret i lämplig tabell (store_id = 0).

Efter att ha lagt till ovanstående attribut till produkten får du denna bild i adminpanelen:

Magento 2: importera produkter direkt till databasen

Inventeringsdata

Från och med version 2.3 i Magento finns det två parallella uppsättningar tabeller som tillhandahåller lagring av lagerinformation (produktkvantitet):

  • cataloginventory_*: gammal struktur;
  • inventory_*: ny struktur (MSI - Multi Source Inventory);

Du måste lägga till lagerdata till båda strukturerna, eftersom den nya strukturen är ännu inte helt oberoende av den gamla (det är mycket troligt att för default lager i den nya strukturen ett bord är inblandat cataloginventory_stock_status som inventory_stock_1).

kataloginventering_

När vi distribuerar Magneto 2.3 har vi initialt 2 poster i store_website, vilket motsvarar två webbplatser - administrativ och huvudklient:

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

Tabell cataloginventory_stock vi har bara en post:

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

Det vill säga, i vår gamla struktur finns det bara ett "lager" (stock) och den är länkad till den administrativa webbplatsen. Lägger till nya via adminpanelen sources/stocks i MSI (ny struktur) resulterar inte i nya poster i cataloginventory_stock.

Lagerdata om produkter i den gamla strukturen registreras initialt i tabeller:

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

lager_

Inledningsvis innehåller den nya strukturen för lagring av lagerdata 1 "källa'(inventory_source):

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

och en "lager'(inventory_stock):

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

«Källa» representerar den fysiska lagringen för produkter (posten innehåller fysiska koordinater och postadress). "lager"är en logisk förening av flera "källor" (inventory_source_stock_link)

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

på den nivå där kopplingen till försäljningskanalen sker (inventory_stock_sales_channel)

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

Att döma av datastrukturen antas olika typer av försäljningskanaler, men som standard endast kopplingen "lager"-"webbplats"(länken till webbplatsen följer webbplatskoden - base).

Ett "lager"kan kopplas till flera"källor"och en "källa"- till flera"lager"(många-till-många relation). Undantagen är standard"källa"Och"lager". De är inte återlänkade till andra enheter (begränsning på kodnivå - felet "Det går inte att spara länk relaterad till standardkälla eller standardlager"). Mer information om MSI-strukturen i Magento 2 finns i artikeln "Lagerhanteringssystem med CQRS och Event Sourcing. Design".

Jag kommer att använda standardkonfigurationen och lägga till all inventeringsinformation till källan default, som är involverad i den försäljningskanal som är kopplad till webbplatsen med koden base (motsvarar framsidan av butiken - se 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);
}

När du har lagt till lagerdata till produkten i adminpanelen får du den här bilden:

Magento 2: importera produkter direkt till databasen

media

När du "manuellt" lägger till en bild till en produkt via adminpanelen, skrivs relevant information ned i följande tabeller:

  • catalog_product_entity_media_gallery: medieregister (bilder och videofiler);
  • catalog_product_entity_media_gallery_value: länka media till produkter och skyltfönster (lokalisering);
  • catalog_product_entity_media_gallery_value_to_entity: länkar endast media till produkter (förmodligen standardmedieinnehåll för produkten);
  • catalog_product_entity_varchar: De roller som bilden används i lagras här;

och själva bilderna sparas i katalogen ./pub/media/catalog/product/x/y/var x и y — första och andra bokstäverna i bildfilens namn. Till exempel fil image.png bör sparas som ./pub/media/catalog/product/i/m/image.png, så att plattformen kan använda den som en bild när man beskriver produkter från katalogen.

Registrera inlagt i ./pub/media/catalog/product/ mediafil (processen för att placera själva filen diskuteras inte i den här artikeln):

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

När den registreras tilldelas en ny mediefil en identifierare.

Vi associerar den registrerade mediefilen med motsvarande produkt för standardskyltfönstret:

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

Vi associerar den registrerade mediefilen med motsvarande produkt utan att vara bundna till något skyltfönster. Det är inte klart var exakt denna data används och varför det är omöjligt att komma åt data från föregående tabell, men denna tabell finns och data skrivs till den när en bild läggs till produkten. Så det är det.

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

En mediafil kan användas med olika roller (motsvarande attributkod anges inom parentes):

  • Bas(image)
  • Liten bild (small_image)
  • Miniatyr (thumbnail)
  • Swatch-bild (swatch_image)

Att länka roller till en mediefil är precis vad som händer i catalog_product_entity_varchar. Bindningskoden liknar koden i "Grundläggande produktegenskaper".

Efter att ha lagt till en bild till produkten i adminpanelen ser det ut så här:

Magento 2: importera produkter direkt till databasen

kategori

Huvudtabeller som innehåller data per kategori:

  • catalog_category_entity: register över kategorier;
  • catalog_category_product: samband mellan produkter och kategorier;
  • catalog_category_entity_*: EAV-attributvärden;

Till en början, i en tom Magento-applikation, innehåller kategoriregistret 2 kategorier (jag förkortade kolumnnamnen: 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|

Kategorin med id=1 är roten till hela Magento-katalogen och är inte tillgänglig vare sig i adminpanelen eller på framsidan. Kategori med id=2 (standard~~POS=TRUNC Kategori) är rotkategorin för huvudsajtens huvudbutik (Huvudwebbbutik) skapas när applikationen distribueras (se. Admin / Butiker / Alla butiker). Dessutom är rotkategorin för själva butiken inte tillgänglig på framsidan, bara dess underkategorier.

Eftersom ämnet för den här artikeln fortfarande är att importera data om produkter, kommer jag inte att använda direkt inmatning i databasen när jag skapar kategorier, utan kommer att använda klasserna som tillhandahålls av Magento själv (modeller och repositories). Direktinmatning i databasen används endast för att associera den importerade produkten med en kategori (kategorin matchas av dess namn, och kategori-id hämtas under matchning):

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

Efter att ha lagt till en produktlänk till kategorierna "Kategori 1" och "Kategori 2", ser produktinformationen i adminpanelen ut ungefär så här:

Magento 2: importera produkter direkt till databasen

Ytterligare åtgärder

När dataimporten är klar måste du utföra följande ytterligare steg:

  • dataindexering: ring i konsolen ./bin/magento indexer:reindex;
  • återskapa webbadresser för produkter/kategorier: du kan använda tillägget "elgentos/regenerate-catalog-urls«

Produkter i adminpanelen efter att ha utfört ytterligare åtgärder:

Magento 2: importera produkter direkt till databasen

och längst fram:

Magento 2: importera produkter direkt till databasen

Sammanfattning

Samma uppsättning produkter (10 stycken) som i föregående artikel importeras minst en storleksordning snabbare (1 sekund mot 10). För att mer exakt uppskatta hastigheten behöver du ett större antal produkter - flera hundra, eller ännu bättre tusentals. Men även med en så liten storlek på indata kan vi dra slutsatsen att användningen av verktygen från Magento (modeller och arkiv) är betydande (jag betonar - mycket!) påskynda utvecklingen av den nödvändiga funktionaliteten, men samtidigt avsevärt (jag betonar - mycket!) minska hastigheten med vilken data kommer in i databasen.

Som ett resultat visade det sig att vattnet var blött och detta är ingen avslöjande. Men nu har jag koden att leka med och kanske kommer till några mer intressanta slutsatser.

Källa: will.com