Magento 2: importer produkter direkte til databasen

В forrige artikel Jeg beskrev processen med at importere produkter til Magento 2 på sædvanlig måde - gennem modeller og repositories. Den sædvanlige metode har en meget lav databehandlingshastighed. Min bærbare computer producerede omkring et produkt i sekundet. I denne fortsættelse overvejer jeg en alternativ måde at importere et produkt på - ved direkte indtastning i databasen, uden om standard Magento 2 mekanismer (modeller, fabrikker, repositories). Rækkefølgen af ​​trin til at importere produkter kan tilpasses til ethvert programmeringssprog, der kan arbejde med MySQL.

Ansvarsfraskrivelse: Magento har færdiglavet funktionalitet til dataimport og højst sandsynligt vil det være nok for dig. Men hvis du har brug for mere fuldstændig kontrol over importprocessen, ikke begrænset til at forberede en CSV-fil til det, du har, velkommen til kat.

Magento 2: importer produkter direkte til databasen

Koden, der er resultatet af at skrive begge artikler, kan ses i Magento-modulet "flancer32/mage2_ext_demo_import". Her er nogle begrænsninger, jeg fulgte for at forenkle demomodulets kode:

  • Produkter er kun oprettet, ikke opdateret.
  • Et lager
  • Kun kategorinavne importeres uden deres struktur
  • Datastrukturer overholder version 2.3

JSON til import af et 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"
}

Oversigt over importens vigtigste stadier

  • registrering af selve produktet
  • forbindelse mellem produkt og hjemmeside
  • grundlæggende produktattributter (EAV)
  • lagerdata (mængde af produkt på lager)
  • medier (billeder)
  • forbindelse med katalogkategorier

Produktregistrering

Grundlæggende produktinformation kan findes 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 mindste information, der kræves for at oprette en post i produktregistret, er:

  • attribute_set_id
  • sku

ekstra:

  • type_id — hvis vi ikke angiver det, så vil 'simple' blive brugt

For at skrive direkte til databasen bruger jeg selve DB-adapteren til 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;
}

Efter registrering af produktet med catalog_product_entity det bliver synligt i admin panelet i produktgitteret (Katalog/Produkter).

Magento 2: importer produkter direkte til databasen

Forholdet mellem produkt og hjemmeside

Produktets tilknytning til siden afgør, i hvilke butikker og udstillinger produktet vil være tilgængeligt 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 til databasen

Grundlæggende produktegenskaber

Det nyregistrerede produkt har endnu ikke et navn eller en beskrivelse. Alt dette er gjort igennem EAV attributter. Her er en liste over grundlæggende produktattributter, der er nødvendige for at produktet kan vises korrekt på forsiden:

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

En separat attribut føjes til et produkt som dette (detaljerne om at opnå identifikatoren og typen af ​​attributten fra dens kode er udeladt):

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 hjælp af attributkoden bestemmer vi dens id og datatype (datetime, decimal, int, text, varchar), skriv derefter dataene for det administrative vindue i den relevante tabel (store_id = 0).

Efter at have tilføjet ovenstående attributter til produktet, får du dette billede i admin panelet:

Magento 2: importer produkter direkte til databasen

Lagerdata

Fra version 2.3 i Magento er der to parallelle sæt tabeller, der giver lagring af lageroplysninger (produktmængde):

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

Du skal tilføje lagerdata til begge strukturer, fordi den nye struktur er endnu ikke helt uafhængig af den gamle (det er meget sandsynligt, at for default lager i den nye struktur er et bord involveret cataloginventory_stock_status som inventory_stock_1).

kataloginventar_

Når vi implementerer Magneto 2.3, har vi i første omgang 2 indgange store_website, som svarer til to websteder - 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 kun én indgang:

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

Det vil sige, i vores gamle struktur er der kun ét "lager" (stock), og den er knyttet til den administrative hjemmeside. Tilføjelse af nye via administrationspanelet sources/stocks i MSI (ny struktur) resulterer ikke i nye poster i cataloginventory_stock.

Lagerdata om produkter i den gamle struktur er indledningsvis registreret 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);
}

beholdning_

I første omgang indeholder den nye struktur til lagring af 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 "lageret"(inventory_stock):

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

«Kilde» repræsenterer det fysiske lager for produkter (posten indeholder fysiske koordinater og postadresse). "Lager"er en logisk forening af flere "kilder" (inventory_source_stock_link)

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

på det niveau, hvor forbindelsen til salgskanalen sker (inventory_stock_sales_channel)

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

At dømme efter datastrukturen antages forskellige typer salgskanaler, men som standard kun forbindelsen "bestand"-"hjemmeside"(linket til hjemmesiden følger hjemmesidens kode - base).

En "lageret"kan knyttes til flere"til kilder"og en "kilde"- til flere"varehuse"(mange-til-mange forhold). Undtagelserne er standard "kilde"Og"lageret". De er ikke genlinket til andre enheder (begrænsning på kodeniveau - fejlen "Kan ikke gemme link relateret til standardkilde eller standardlager"). Flere detaljer om MSI-strukturen i Magento 2 kan findes i artiklen "Lagerstyringssystem ved hjælp af CQRS og Event Sourcing. Design".

Jeg vil bruge standardkonfigurationen og tilføje alle beholdningsoplysninger til kilden default, som er involveret i salgskanalen tilknyttet hjemmesiden med koden base (svarer til forenden af ​​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);
}

Efter at have tilføjet lagerdata til produktet i administrationspanelet får du dette billede:

Magento 2: importer produkter direkte til databasen

Medier

Når du "manuelt" tilføjer et billede til et produkt gennem admin panelet, er de relevante oplysninger skrevet ned i følgende tabeller:

  • catalog_product_entity_media_gallery: medieregister (billeder og videofiler);
  • catalog_product_entity_media_gallery_value: linke medier til produkter og udstillingsvinduer (lokalisering);
  • catalog_product_entity_media_gallery_value_to_entity: kun at linke medier til produkter (formentlig standardmedieindhold for produktet);
  • catalog_product_entity_varchar: De roller, som billedet bruges i, gemmes her;

og selve billederne gemmes i biblioteket ./pub/media/catalog/product/x/y/Hvor x и y — første og andet bogstav i billedfilens navn. For eksempel fil image.png skal gemmes som ./pub/media/catalog/product/i/m/image.png, så platformen kan bruge det som billede, når produkter fra kataloget skal beskrives.

Tilmelding lagt ind ./pub/media/catalog/product/ mediefil (processen med at placere selve filen er ikke diskuteret i denne artikel):

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

Vi knytter den registrerede mediefil til det tilsvarende produkt for standard butiksfacade:

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 forbinder den registrerede mediefil med det tilsvarende produkt uden at være bundet til nogen butiksfacade. Det er ikke klart, hvor præcist disse data bruges, og hvorfor det er umuligt at få adgang til dataene fra den foregående tabel, men denne tabel eksisterer, og dataene skrives til den, når et billede føjes 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 bruges med forskellige roller (den tilsvarende attributkode er angivet i parentes):

  • Grundlag(image)
  • Lille billede (small_image)
  • Miniaturebillede (thumbnail)
  • Swatch-billede (swatch_image)

At linke roller til en mediefil er præcis, hvad der sker i catalog_product_entity_varchar. Den bindende kode ligner koden i "Grundlæggende produktegenskaber".

Efter at have tilføjet et billede til produktet i admin panelet ser det sådan ud:

Magento 2: importer produkter direkte til databasen

kategori

Hovedtabeller med data efter kategori:

  • catalog_category_entity: register over kategorier;
  • catalog_category_product: forbindelse mellem produkter og kategorier;
  • catalog_category_entity_*: EAV-attributværdier;

Til at begynde med, i en tom Magento-applikation, indeholder kategoriregistret 2 kategorier (jeg forkortede kolonnenavnene: crtcreated_at, updupdated_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 roden til hele Magento-kataloget og er hverken tilgængelig i adminpanelet eller på forsiden. Kategori med id=2 (Standard Kategori) er rodkategorien for hovedwebstedets hovedbutik (Hovedwebstedsbutik) oprettet ved implementering af applikationen (se. Admin / Butikker / Alle butikker). Desuden er selve butikkens rodkategori heller ikke tilgængelig foran, kun dens underkategorier.

Da emnet for denne artikel stadig er import af data på produkter, vil jeg ikke bruge direkte indtastning i databasen, når jeg opretter kategorier, men vil bruge klasserne, som Magento selv leverer (modeller og repositories). Direkte indtastning i databasen bruges kun til at knytte det importerede produkt til en kategori (kategorien matches af dens navn, og kategori-id'et 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);
}

Efter at have tilføjet et produktlink til kategorierne "Kategori 1" og "Kategori 2", ser produktdetaljerne i administrationspanelet nogenlunde sådan ud:

Magento 2: importer produkter direkte til databasen

Yderligere aktioner

Når dataimporten er fuldført, skal du udføre følgende yderligere trin:

  • dataindeksering: ring i konsollen ./bin/magento indexer:reindex;
  • regenerering af URL'er for produkter/kategorier: du kan bruge udvidelsen "elgentos/regenerate-catalog-urls«

Produkter i administratorpanelet efter at have udført yderligere handlinger:

Magento 2: importer produkter direkte til databasen

og forrest:

Magento 2: importer produkter direkte til databasen

Resumé

Det samme sæt produkter (10 styk) som i den foregående artikel importeres mindst en størrelsesorden hurtigere (1 sekund mod 10). For mere præcist at estimere hastigheden har du brug for et større antal produkter - flere hundrede, eller endnu bedre tusinder. Men selv med en så lille størrelse af inputdata kan vi konkludere, at brugen af ​​værktøjerne leveret af Magento (modeller og arkiver) er betydelig (jeg understreger - meget!) fremskynde udviklingen af ​​den nødvendige funktionalitet, men samtidig betydeligt (jeg understreger - meget!) reducere hastigheden, hvormed data kommer ind i databasen.

Som et resultat viste vandet sig at være vådt, og dette er ikke en åbenbaring. Men nu har jeg koden at lege med og måske komme til nogle mere interessante konklusioner.

Kilde: www.habr.com