Magento 2: importer produkter direkte inn i databasen

В forrige artikkel Jeg beskrev prosessen med å importere produkter til Magento 2 på vanlig måte – gjennom modeller og repositories. Den vanlige metoden har svært lav databehandlingshastighet. Den bærbare datamaskinen min produserte omtrent ett produkt per sekund. I denne fortsettelsen vurderer jeg en alternativ måte å importere et produkt på - ved å gå direkte inn i databasen, omgå standard Magento 2-mekanismer (modeller, fabrikker, depoter). Sekvensen av trinn for å importere produkter kan tilpasses et hvilket som helst programmeringsspråk som kan fungere med MySQL.

Ansvarsfraskrivelse: Magento har ferdig funksjonalitet for dataimport og mest sannsynlig vil det være nok for deg. Men hvis du trenger mer fullstendig kontroll over importprosessen, ikke begrenset til å forberede en CSV-fil for det du har, velkommen til cat.

Magento 2: importer produkter direkte inn i databasen

Koden som er et resultat av å skrive begge artiklene kan sees i Magento-modulen "flancer32/mage2_ext_demo_import". Her er noen begrensninger jeg fulgte for å forenkle demomodulkoden:

  • Produkter opprettes kun, ikke oppdatert.
  • Ett lager
  • Bare kategorinavn importeres, uten struktur
  • Datastrukturer er i samsvar med versjon 2.3

JSON for import av ett enkelt 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"
}

Oversikt over hovedstadiene av import

  • registrering av selve produktet
  • forbindelse mellom produkt og nettside
  • grunnleggende produktattributter (EAV)
  • lagerdata (antall produkt på lager)
  • media (bilder)
  • forbindelse med katalogkategorier

Produkt registrering

Grunnleggende produktinformasjon finner du 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`)
)

Minimumsinformasjonen som kreves for å opprette en oppføring i produktregisteret er:

  • attribute_set_id
  • sku

ytterligere:

  • type_id - hvis vi ikke spesifiserer det, vil 'enkel' bli brukt

For å skrive direkte til databasen bruker jeg DB-adapteren til Magento selv:

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

Etter å ha registrert produktet hos catalog_product_entity det blir synlig i administrasjonspanelet, i produktnettet (Katalog/produkter).

Magento 2: importer produkter direkte inn i databasen

Forholdet mellom produkt og nettside

Tilknytningen mellom produktet og nettstedet avgjør i hvilke butikker og utstillinger produktet vil være tilgjengelig foran.

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: importer produkter direkte inn i databasen

Grunnleggende produktegenskaper

Det nyregistrerte produktet har ennå ikke navn eller beskrivelse. Alt dette er gjort gjennom EAV-attributter. Her er en liste over grunnleggende produktattributter som er nødvendige for at produktet skal vises riktig på forsiden:

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

Et eget attributt legges til et produkt som dette (detaljene for å få identifikatoren og typen av attributtet fra koden er utelatt):

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

Ved å bruke attributtkoden bestemmer vi dens id og datatype (datetime, decimal, int, text, varchar), skriv deretter dataene for det administrative vinduet inn i den aktuelle tabellen (store_id = 0).

Etter å ha lagt til attributtene ovenfor til produktet, får du dette bildet i administrasjonspanelet:

Magento 2: importer produkter direkte inn i databasen

Inventardata

Fra og med versjon 2.3 i Magento er det to parallelle sett med tabeller som gir lagring av lagerinformasjon (produktmengde):

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

Du må legge til inventardata til begge strukturene, fordi den nye strukturen er ennå ikke helt uavhengig av den gamle (det er svært sannsynlig at for default lager i den nye strukturen et bord er involvert cataloginventory_stock_status som inventory_stock_1).

katalogbeholdning_

Når vi distribuerer Magneto 2.3, har vi i utgangspunktet 2 oppføringer store_website, som tilsvarer to nettsteder - administrativ og hovedklient:

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

Bord cataloginventory_stock vi har bare én oppføring:

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

Det vil si at i vår gamle struktur er det bare ett "lager" (stock) og den er koblet til det administrative nettstedet. Legger til nye gjennom administrasjonspanelet sources/stocks i MSI (ny struktur) resulterer ikke i nye oppføringer i cataloginventory_stock.

Lagerdata om produkter i den gamle strukturen blir først registrert 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);
}

inventar_

I utgangspunktet inneholder den nye strukturen for lagring av lagerdata 1 "kilde"(inventory_source):

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

og en "lager"(inventory_stock):

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

«Kilde» representerer den fysiske lagringen for produkter (posten inneholder fysiske koordinater og postadresse). "Lager"er en logisk forening av flere "kilder" (inventory_source_stock_link)

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

på nivået der koblingen til salgskanalen skjer (inventory_stock_sales_channel)

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

Ut fra datastrukturen å dømme, antas det ulike typer salgskanaler, men som standard bare tilkoblingen "lager"-"nettsted"(lenken til nettstedet følger nettstedskoden - base).

En "lager"kan knyttes til flere"til kilder"og en "kilde"- til flere"varehus"(mange-til-mange forhold). Unntakene er standard "kilde"Og"lager". De er ikke koblet til andre enheter (begrensning på kodenivå - feilen "Kan ikke lagre koblingen relatert til standardkilde eller standardlager"). Flere detaljer om MSI-strukturen i Magento 2 finner du i artikkelen "Lagerstyringssystem som bruker CQRS og Event Sourcing. Design".

Jeg vil bruke standardkonfigurasjonen og legge til all inventarinformasjon til kilden default, som er involvert i salgskanalen knyttet til nettstedet med koden base (tilsvarer forsiden av butikken - 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);
}

Etter å ha lagt til lagerdata til produktet i administrasjonspanelet, får du dette bildet:

Magento 2: importer produkter direkte inn i databasen

media

Når du "manuelt" legger til et bilde til et produkt gjennom administrasjonspanelet, skrives den relevante informasjonen ned i følgende tabeller:

  • catalog_product_entity_media_gallery: medieregister (bilder og videofiler);
  • catalog_product_entity_media_gallery_value: koble media til produkter og utstillingsvinduer (lokalisering);
  • catalog_product_entity_media_gallery_value_to_entity: kun koble media til produkter (antagelig standard medieinnhold for produktet);
  • catalog_product_entity_varchar: Rollene som bildet brukes i lagres her;

og selve bildene lagres i katalogen ./pub/media/catalog/product/x/y/Der x и y — den første og andre bokstaven i bildefilnavnet. For eksempel fil image.png skal lagres som ./pub/media/catalog/product/i/m/image.png, slik at plattformen kan bruke den som et bilde når den skal beskrive produkter fra katalogen.

Registrering lagt inn ./pub/media/catalog/product/ mediefil (prosessen med å plassere selve filen er ikke omtalt i denne artikkelen):

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 er registrert, tildeles en ny mediefil en identifikator.

Vi knytter den registrerte mediefilen til det tilsvarende produktet for standard butikkfront:

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 knytter den registrerte mediefilen til det tilsvarende produktet uten å være knyttet til noen butikkfront. Det er ikke klart hvor nøyaktig disse dataene brukes og hvorfor det er umulig å få tilgang til dataene fra forrige tabell, men denne tabellen eksisterer og dataene skrives til den når et bilde legges til produktet. Så det er 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 mediefil kan brukes med forskjellige roller (den tilsvarende attributtkoden er angitt i parentes):

  • Utgangspunkt (image)
  • Lite bilde (small_image)
  • Miniatyrbilde (thumbnail)
  • Swatch-bilde (swatch_image)

Å koble roller til en mediefil er akkurat det som skjer i catalog_product_entity_varchar. Bindingskoden ligner på koden i "Grunnleggende produktegenskaper".

Etter å ha lagt til et bilde til produktet i administrasjonspanelet ser det slik ut:

Magento 2: importer produkter direkte inn i databasen

kategori

Hovedtabeller som inneholder data etter kategori:

  • catalog_category_entity: register over kategorier;
  • catalog_category_product: sammenheng mellom produkter og kategorier;
  • catalog_category_entity_*: EAV-attributtverdier;

Til å begynne med, i en tom Magento-applikasjon, inneholder kategoriregisteret 2 kategorier (jeg forkortet kolonnenavnene: 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|

Kategorien med id=1 er roten til hele Magento-katalogen og er ikke tilgjengelig verken i administrasjonspanelet eller på forsiden. Kategori med id=2 (standard kategori) er rotkategorien for hovednettstedets hovedbutikk (Hovednettstedsbutikk) opprettet når applikasjonen er distribuert (se. Admin / Butikker / Alle butikker). Dessuten er rotkategorien til selve butikken heller ikke tilgjengelig foran, bare underkategoriene.

Siden emnet for denne artikkelen fortsatt er import av data på produkter, vil jeg ikke bruke direkte oppføring i databasen når jeg oppretter kategorier, men vil bruke klassene levert av Magento selv (modeller og repositories). Direkte oppføring i databasen brukes bare for å knytte det importerte produktet til en kategori (kategorien samsvarer med navnet, og kategori-ID-en hentes under matching):

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

Etter å ha lagt til en produktlenke til kategoriene «Kategori 1» og «Kategori 2», ser produktdetaljene i administrasjonspanelet omtrent slik ut:

Magento 2: importer produkter direkte inn i databasen

Ytterligere handlinger

Når dataimporten er fullført, må du fullføre følgende ekstra trinn:

  • dataindeksering: ring inn konsollen ./bin/magento indexer:reindex;
  • regenerering av nettadresser for produkter/kategorier: du kan bruke utvidelsen "elgentos/regenerate-catalog-urls«

Produkter i administrasjonspanelet etter å ha utført ytterligere handlinger:

Magento 2: importer produkter direkte inn i databasen

og foran:

Magento 2: importer produkter direkte inn i databasen

Oppsummering

Det samme settet med produkter (10 stykker) som i forrige artikkel importeres minst en størrelsesorden raskere (1 sekund mot 10). For å estimere hastigheten mer nøyaktig, trenger du et større antall produkter - flere hundre, eller enda bedre tusenvis. Men selv med en så liten størrelse på inndata, kan vi konkludere med at bruken av verktøyene levert av Magento (modeller og depoter) er betydelig (jeg understreker - mye!) fremskynde utviklingen av den nødvendige funksjonaliteten, men samtidig betydelig (jeg understreker - mye!) redusere hastigheten som data kommer inn i databasen med.

Som et resultat viste vannet seg å være vått, og dette er ikke en åpenbaring. Men nå har jeg koden å leke med og kanskje komme til noen mer interessante konklusjoner.

Kilde: www.habr.com