Magento 2: importe produtos diretamente para o banco de dados

В artigo anterior Descrevi o processo de importação de produtos para o Magento 2 da maneira usual - por meio de modelos e repositórios. O método usual possui uma velocidade de processamento de dados muito baixa. Meu laptop estava produzindo cerca de um produto por segundo. Nesta continuação, considero uma forma alternativa de importar um produto - por entrada direta no banco de dados, contornando os mecanismos padrão do Magento 2 (modelos, fábricas, repositórios). A sequência de passos para importação de produtos pode ser adaptada a qualquer linguagem de programação que funcione com MySQL.

Aviso Legal: Magento tem funcionalidades prontas para importação de dados e, provavelmente, será o suficiente para você. Porém, se você precisa de um controle mais completo sobre o processo de importação, não se limitando a preparar um arquivo CSV para o que você possui, seja bem-vindo ao cat.

Magento 2: importe produtos diretamente para o banco de dados

O código resultante da escrita de ambos os artigos pode ser visualizado no módulo Magento "flancer32/mage2_ext_demo_import". Aqui estão algumas restrições que segui para simplificar o código do módulo de demonstração:

  • Os produtos são apenas criados, não atualizados.
  • Um armazém
  • Somente nomes de categorias são importados, sem sua estrutura
  • As estruturas de dados estão em conformidade com a versão 2.3

JSON para importar um ú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"
}

Visão geral das principais etapas da importação

  • registro do próprio produto
  • conexão entre produto e site
  • atributos básicos do produto (EAV)
  • dados de estoque (quantidade de produto em estoque)
  • mídia (fotos)
  • conexão com categorias de catálogo

Registo de produto

Informações básicas do produto podem ser encontradas em 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`)
)

As informações mínimas necessárias para criar uma entrada no cadastro do produto são:

  • attribute_set_id
  • sku

adicional:

  • type_id — se não especificarmos, então 'simples' será usado

Para escrever diretamente no banco de dados, utilizo o adaptador DB do próprio 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;
}

Depois de registrar o produto no catalog_product_entity fica visível no painel de administração, na grade de produtos (Catálogo/Produtos).

Magento 2: importe produtos diretamente para o banco de dados

Relação entre produto e site

A associação do produto ao site determina em quais lojas e expositores o produto estará disponível na vitrine.

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: importe produtos diretamente para o banco de dados

Atributos básicos do produto

O produto recém-registrado ainda não possui nome ou descrição. Tudo isso é feito através Atributos EAV. Aqui está uma lista de atributos básicos do produto que são necessários para que o produto seja exibido corretamente na frente:

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

Um atributo separado é adicionado a um produto como este (os detalhes para obter o identificador e o tipo do atributo de seu código são omitidos):

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 seu id e tipo de dados (datetime, decimal, int, text, varchar) e, em seguida, grave os dados da janela administrativa na tabela apropriada (store_id = 0).

Depois de adicionar os atributos acima ao produto, você obtém esta imagem no painel de administração:

Magento 2: importe produtos diretamente para o banco de dados

Dados de inventário

A partir da versão 2.3 no Magento, existem dois conjuntos paralelos de tabelas que fornecem armazenamento de informações de estoque (quantidade de produtos):

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

Você precisa adicionar dados de inventário a ambas as estruturas, porque a nova estrutura ainda não é completamente independente da antiga (é muito provável que para default armazém na nova estrutura uma tabela está envolvida cataloginventory_stock_status como inventory_stock_1).

catálogoinventário_

Ao implantar o Magneto 2.3, inicialmente temos 2 entradas em store_website, que corresponde a dois sites - 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|

Mesa cataloginventory_stock temos apenas uma entrada:

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

Ou seja, na nossa estrutura antiga existe apenas um “armazém” (stock) e está vinculado ao site administrativo. Adicionando novos através do painel de administração sources/stocks no MSI (nova estrutura) não resulta em novas entradas no cataloginventory_stock.

Os dados de estoque de produtos na estrutura antiga são inicialmente registrados em tabelas:

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

inventário_

Inicialmente, a nova estrutura de armazenamento de dados de estoque contém 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 um "armazém"(inventory_stock):

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

«Fonte» representa o armazenamento físico dos produtos (o registro contém coordenadas físicas e endereço para correspondência). "Armazém"é uma união lógica de várias" fontes "(inventory_source_stock_link)

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

no nível em que ocorre a conexão com o canal de vendas (inventory_stock_sales_channel)

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

A julgar pela estrutura de dados, são assumidos vários tipos de canais de vendas, mas por padrão apenas a conexão “estoque"-"site do Network Development Group"(o link para o site segue o código do site - base).

Um "armazém"pode ​​ser vinculado a vários"fontes"e um "fonte" - para vários "armazéns"(relacionamento muitos-para-muitos). As exceções são padrão "fonte"E"armazém". Eles não são vinculados novamente a outras entidades (limitação no nível do código - o erro “Não é possível salvar o link relacionado à origem padrão ou ao estoque padrão"). Mais detalhes sobre a estrutura MSI no Magento 2 podem ser encontrados no artigo “Sistema de gerenciamento de armazém utilizando CQRS e Event Sourcing. Projeto".

Usarei a configuração padrão e adicionarei todas as informações de inventário à fonte default, que está envolvido no canal de vendas associado ao site com o código base (corresponde à fachada da loja - 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);
}

Depois de adicionar dados de inventário ao produto no painel de administração, você terá esta imagem:

Magento 2: importe produtos diretamente para o banco de dados

mídia

Ao adicionar “manualmente” uma imagem a um produto através do painel de administração, as informações relevantes são anotadas nas seguintes tabelas:

  • catalog_product_entity_media_gallery: registro de mídia (arquivos de imagens e vídeos);
  • catalog_product_entity_media_gallery_value: vinculação de mídia a produtos e vitrines (localização);
  • catalog_product_entity_media_gallery_value_to_entity: vinculando mídia apenas a produtos (presumivelmente conteúdo de mídia padrão para o produto);
  • catalog_product_entity_varchar: As funções nas quais a imagem é usada são armazenadas aqui;

e as próprias imagens são salvas no diretório ./pub/media/catalog/product/x/y/Onde x и y — a primeira e a segunda letras do nome do arquivo de imagem. Por exemplo, arquivo image.png deve ser salvo como ./pub/media/catalog/product/i/m/image.png, para que a plataforma possa utilizá-lo como imagem na descrição dos produtos do catálogo.

Cadastro postado em ./pub/media/catalog/product/ arquivo de mídia (o processo de colocação do arquivo em si não é discutido 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;
}

Quando registrado, um novo arquivo de mídia recebe um identificador.

Associamos o arquivo de mídia cadastrado ao produto correspondente da vitrine padrão:

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

Associamos o arquivo de mídia cadastrado ao produto correspondente sem estarmos vinculados a nenhuma vitrine. Não está claro onde exatamente esses dados são usados ​​e por que é impossível acessar os dados da tabela anterior, mas esta tabela existe e os dados são gravados nela quando uma imagem é adicionada ao produto. Então é isso.

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

Um arquivo de mídia pode ser usado com diferentes funções (o código do atributo correspondente é indicado entre parênteses):

  • Base(image)
  • Imagem pequena (small_image)
  • Miniatura (thumbnail)
  • Imagem de amostra (swatch_image)

Vincular funções a um arquivo de mídia é exatamente o que acontece em catalog_product_entity_varchar. O código de ligação é semelhante ao código no "Atributos básicos do produto".

Depois de adicionar uma imagem ao produto no painel de administração, ele ficará assim:

Magento 2: importe produtos diretamente para o banco de dados

categoria

Principais tabelas contendo dados por categoria:

  • catalog_category_entity: cadastro de categorias;
  • catalog_category_product: conexão entre produtos e categorias;
  • catalog_category_entity_*: Valores de atributos EAV;

Inicialmente, em uma aplicação Magento vazia, o registro de categorias contém 2 categorias (encurtei os nomes das colunas: 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 categoria com id=1 é a raiz de todo o catálogo Magento e não está disponível nem no painel de administração nem na página inicial. Categoria com id=2 (Categoria Padrão) é a categoria raiz da loja principal do site principal (Loja principal do site) criado quando o aplicativo é implantado (consulte. Administrador / Lojas / Todas as lojas). Além disso, a categoria raiz da loja em si também não está disponível na frente, apenas suas subcategorias.

Como o tema deste artigo ainda é importação de dados de produtos, não utilizarei a entrada direta no banco de dados na criação de categorias, mas utilizarei as classes fornecidas pelo próprio Magento (modelos e repositórios). A entrada direta no banco de dados é usada apenas para associar o produto importado a uma categoria (a categoria é correspondida por seu nome e o ID da categoria é recuperado durante a correspondência):

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

Depois de adicionar um link de produto às categorias “Categoria 1” e “Categoria 2”, os detalhes do produto no painel de administração são mais ou menos assim:

Magento 2: importe produtos diretamente para o banco de dados

Ações adicionais

Assim que a importação de dados for concluída, você precisará concluir as seguintes etapas adicionais:

  • indexação de dados: chamada no console ./bin/magento indexer:reindex;
  • regenerando URLs para produtos/categorias: você pode usar a extensão “elgentos/regenerate-catalog-urls«

Produtos no painel de administração após realizar ações adicionais:

Magento 2: importe produtos diretamente para o banco de dados

e na frente:

Magento 2: importe produtos diretamente para o banco de dados

Resumo

O mesmo conjunto de produtos (10 peças) do artigo anterior é importado pelo menos uma ordem de grandeza mais rápida (1 segundo versus 10). Para estimar a velocidade com mais precisão, você precisa de um número maior de produtos - várias centenas, ou melhor ainda, milhares. Porém, mesmo com um tamanho tão pequeno de dados de entrada, podemos concluir que o uso das ferramentas disponibilizadas pelo Magento (modelos e repositórios) é significativo (enfatizo - muito!) acelerar o desenvolvimento da funcionalidade necessária, mas ao mesmo tempo significativamente (enfatizo - muito!) reduz a velocidade com que os dados chegam ao banco de dados.

Como resultado, a água ficou molhada e isso não é uma revelação. No entanto, agora tenho o código para brincar e talvez chegar a algumas conclusões mais interessantes.

Fonte: habr.com