Magento 2: importa produtos directamente na base de datos

В artigo anterior Describín o proceso de importación de produtos a Magento 2 do xeito habitual: a través de modelos e repositorios. O método habitual ten unha velocidade de procesamento de datos moi baixa. O meu portátil producía aproximadamente un produto por segundo. Nesta continuación, considero unha forma alternativa de importar un produto: mediante a entrada directa na base de datos, evitando os mecanismos estándar de Magento 2 (modelos, fábricas, repositorios). A secuencia de pasos para importar produtos pódese adaptar a calquera linguaxe de programación que poida funcionar con MySQL.

retratação: Magento ten unha funcionalidade preparada para importación de datos e, moi probablemente, será suficiente para ti. Non obstante, se necesitas un control máis completo sobre o proceso de importación, sen limitarse a preparar un ficheiro CSV para o que tes, benvido a cat.

Magento 2: importa produtos directamente na base de datos

O código resultante de escribir ambos artigos pódese ver no módulo de Magento "flancer32/mage2_ext_demo_import". Aquí tes algunhas restricións que seguín para simplificar o código do módulo de demostración:

  • Os produtos só se crean, non se actualizan.
  • Un almacén
  • Só se importan os nomes de categoría, sen a súa estrutura
  • As estruturas de datos cumpren coa versión 2.3

JSON para importar un único produto:

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

Visión xeral das principais etapas da importación

  • rexistro do propio produto
  • conexión entre o produto e o sitio web
  • Atributos básicos do produto (EAV)
  • datos de inventario (cantidade de produto en stock)
  • medios (fotos)
  • conexión con categorías de catálogo

Rexistro do produto

A información básica do produto pódese atopar en 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`)
)

A información mínima necesaria para crear unha entrada no rexistro de produtos é:

  • attribute_set_id
  • sku

adicional:

  • type_id — se non o especificamos, empregarase "simple".

Para escribir directamente na base de datos, uso o adaptador de base de datos de 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;
}

Despois de rexistrar o produto con catalog_product_entity faise visible no panel de administración, na grade do produto (Catálogo/Produtos).

Magento 2: importa produtos directamente na base de datos

Relación entre produto e sitio web

A asociación do produto co sitio determina en que tendas e exposicións estará dispoñible o produto na parte frontal.

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: importa produtos directamente na base de datos

Atributos básicos do produto

O produto recentemente rexistrado aínda non ten un nome nin descrición. Todo isto faise a través Atributos EAV. Aquí tes unha lista de atributos básicos do produto que son necesarios para que o produto se mostre correctamente na parte frontal:

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

Engádese un atributo separado a un produto coma este (omítense os detalles para obter o identificador e o tipo do atributo do seu código):

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

Usando o código do atributo, determinamos o seu id e o tipo de datos (datetime, decimal, int, text, varchar), escriba os datos para a xanela administrativa na táboa apropiada (store_id = 0).

Despois de engadir os atributos anteriores ao produto, obtén esta imaxe no panel de administración:

Magento 2: importa produtos directamente na base de datos

Datos de inventario

A partir da versión 2.3 en Magento, hai dous conxuntos paralelos de táboas que proporcionan almacenamento de información de inventario (cantidade de produto):

  • cataloginventory_*: estrutura antiga;
  • inventory_*: nova estrutura (MSI - Multi Source Inventory);

Debe engadir datos de inventario a ambas estruturas, porque a nova estrutura aínda non é completamente independente da antiga (é moi probable que para default almacén na nova estrutura está implicada unha mesa cataloginventory_stock_status como inventory_stock_1).

catalogo inventario_

Cando implementamos Magneto 2.3 inicialmente temos 2 entradas store_website, que corresponde a dous sitios: administrativo e cliente 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|

Táboa cataloginventory_stock só temos unha entrada:

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

É dicir, na nosa antiga estrutura só hai un "almacén" (stock) e está vinculado á páxina web administrativa. Engadindo novos a través do panel de administración sources/stocks en MSI (nova estrutura) non dá lugar a novas entradas en cataloginventory_stock.

Os datos de inventario dos produtos da antiga estrutura rexístranse inicialmente en táboas:

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

inventario_

Inicialmente, a nova estrutura para almacenar datos de inventario contén 1 "fonte'(inventory_source):

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

e un"almacén'(inventory_stock):

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

«Orixe» representa o almacenamento físico dos produtos (o rexistro contén as coordenadas físicas e o enderezo postal). "Almacén"é unha unión lóxica de varias "fontes" (inventory_source_stock_link)

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

no nivel no que se produce a conexión coa canle de vendas (inventory_stock_sales_channel)

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

A xulgar pola estrutura de datos, asúmense varios tipos de canles de venda, pero por defecto só a conexión "stock"-" "(a ligazón ao sitio web segue o código do sitio web - base).

Un"almacén"pode ​​estar ligado a varios"fontes"e un"fonte"- a varios"almacéns"(relación de moitos a moitos). As excepcións son por defecto "fonte"E"almacén". Non están ligados de novo a outras entidades (limitación a nivel de código - o erro "Non se pode gardar a ligazón relacionada coa fonte predeterminada ou o stock predeterminado"). Podes atopar máis detalles sobre a estrutura MSI en Magento 2 no artigo "Sistema de xestión de almacén mediante CQRS e Event Sourcing. Deseño«.

Usarei a configuración predeterminada e engadirei toda a información do inventario á fonte default, que está implicado na canle de vendas asociada ao sitio web co código base (corresponde á parte frontal da tenda - ver 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);
}

Despois de engadir datos de inventario ao produto no panel de administración, obtén esta imaxe:

Magento 2: importa produtos directamente na base de datos

Medios

Cando se engade "manualmente" unha imaxe a un produto a través do panel de administración, a información relevante escríbese nas seguintes táboas:

  • catalog_product_entity_media_gallery: rexistro multimedia (imaxes e ficheiros de vídeo);
  • catalog_product_entity_media_gallery_value: vinculación de medios a produtos e vitrinas (localización);
  • catalog_product_entity_media_gallery_value_to_entity: vinculación de medios só a produtos (presumiblemente contido multimedia predeterminado para o produto);
  • catalog_product_entity_varchar: aquí gárdanse os roles nos que se usa a imaxe;

e as propias imaxes gárdanse no directorio ./pub/media/catalog/product/x/y/onde x и y — a primeira e a segunda letras do nome do ficheiro de imaxe. Por exemplo, arquivo image.png debe gardarse como ./pub/media/catalog/product/i/m/image.png, para que a plataforma poida utilizalo como imaxe á hora de describir produtos do catálogo.

Rexistrarse publicado en ./pub/media/catalog/product/ ficheiro multimedia (o proceso de colocación do ficheiro non se discute neste artigo):

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

Cando se rexistra, asígnaselle un identificador a un novo ficheiro multimedia.

Asociamos o ficheiro multimedia rexistrado co produto correspondente para o escaparate predeterminado:

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

Asociamos o ficheiro multimedia rexistrado co produto correspondente sen estar vinculados a ningún escaparate. Non está claro onde se usan exactamente estes datos e por que é imposible acceder aos datos da táboa anterior, pero esta táboa existe e os datos escriben nela cando se engade unha imaxe ao produto. Entón é iso.

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

Pódese usar un ficheiro multimedia con diferentes roles (o código de atributo correspondente indícase entre parénteses):

  • Base (image)
  • Imaxe pequena (small_image)
  • Miniatura (thumbnail)
  • Imaxe de mostra (swatch_image)

Enlazar roles a un ficheiro multimedia é exactamente o que ocorre en catalog_product_entity_varchar. O código vinculante é semellante ao código do "Atributos básicos do produto«.

Despois de engadir unha imaxe ao produto no panel de administración, ten o seguinte aspecto:

Magento 2: importa produtos directamente na base de datos

Категории

Táboas principais que conteñen datos por categoría:

  • catalog_category_entity: rexistro de categorías;
  • catalog_category_product: conexión entre produtos e categorías;
  • catalog_category_entity_*: valores de atributos EAV;

Inicialmente, nunha aplicación Magento baleira, o rexistro de categorías contén 2 categorías (acurtei os nomes das columnas: 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|

A categoría con id=1 é a raíz de todo o catálogo de Magento e non está dispoñible nin no panel de administración nin na portada. Categoría con id=2 (Categoría predeterminada) é a categoría raíz para a tenda principal do sitio principal (Tenda web principal) creado cando se implanta a aplicación (ver. Administración / Tendas / Todas as tendas). Ademais, a categoría raíz da propia tenda tampouco está dispoñible na parte frontal, só as súas subcategorías.

Dado que o tema deste artigo aínda é a importación de datos sobre produtos, non vou utilizar a entrada directa na base de datos ao crear categorías, senón que utilizarei as clases que proporciona o propio Magento (modelos e repositorios). A entrada directa na base de datos utilízase só para asociar o produto importado cunha categoría (a categoría corresponde co seu nome e obtén o identificador da categoría durante a coincidencia):

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

Despois de engadir unha ligazón de produto ás categorías "Categoría 1" e "Categoría 2", os detalles do produto no panel de administración parecen así:

Magento 2: importa produtos directamente na base de datos

Accións adicionais

Unha vez completada a importación de datos, cómpre completar os seguintes pasos adicionais:

  • indexación de datos: chamada na consola ./bin/magento indexer:reindex;
  • rexenerando URL para produtos/categorías: podes usar a extensión "elgentos/regenerate-catalog-urls«

Produtos no panel de administración despois de realizar accións adicionais:

Magento 2: importa produtos directamente na base de datos

e na fronte:

Magento 2: importa produtos directamente na base de datos

Resumo

O mesmo conxunto de produtos (10 pezas) que no artigo anterior impórtase polo menos unha orde de magnitude máis rápido (1 segundo fronte a 10). Para estimar con máis precisión a velocidade, necesitas un número maior de produtos: varios centos, ou mellor aínda miles. Non obstante, aínda cun tamaño tan pequeno de datos de entrada, podemos concluír que o uso das ferramentas proporcionadas por Magento (modelos e repositorios) é significativo (subliño - moi!) acelerar o desenvolvemento da funcionalidade necesaria, pero ao mesmo tempo significativamente (subliño - moi!) Reduce a velocidade á que os datos entran na base de datos.

Como resultado, a auga resultou mollada e isto non é unha revelación. Non obstante, agora teño o código co que xogar e quizais chegue a algunhas conclusións máis interesantes.

Fonte: www.habr.com