Magento 2: importação de produtos diretamente no banco de dados

Em artigo anterior Descrevi o processo de importação de produtos no Magento 2 de maneira comum — através de modelos e repositórios. O método convencional tem uma velocidade de processamento de dados bastante baixa. No meu laptop, o resultado era aproximadamente um produto por segundo. Neste artigo, vou abordar uma alternativa para a importação de produtos — gravação direta no banco de dados, ignorando os mecanismos padrão do Magento 2 (modelos, fábricas, repositórios). A sequência de etapas para garantir a importação de produtos pode ser adaptada para qualquer linguagem de programação capaz de trabalhar com MySQL.

Aviso Legal: O Magento possui uma funcionalidade pronta para importação de dados e, provavelmente, você vai precisar disso. No entanto, se você precisa de um controle mais completo sobre o processo de importação, que não se limita à preparação de um arquivo CSV para o que já existe — sinta-se à vontade para continuar.

Magento 2: importação de produtos diretamente no banco de dados

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

  • Os produtos são apenas criados, não atualizados.
  • Um armazém
  • Apenas os nomes das categorias são importados, sem sua estrutura
  • As estruturas de dados correspondem à versão 2.3

JSON para importação de um único produto:

{
  "sku": "MVA20D-UBV-3",
  "name": "Tampa para selagem VA47-29 IEK",
  "desc": "Garantia de acesso a dispositivos ...",
  "desc_short": "A Tampa para selagem VA47-29 IEK é destinada a ...",
  "price": 5.00,
  "qty": 25,
  "categories": ["Categoria 1", "Categoria 2"],
  "image_path": "mva20d_ubv_3.png"
}

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

  • registro do próprio produto
  • ligação do produto ao site
  • atributos básicos do produto (EAV)
  • dados de inventário (quantidade de produto em estoque)
  • mídia (imagens)
  • ligação com categorias do catálogo

Registro do produto

As informações básicas sobre o produto estão 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`)
)

Informações mínimas necessárias para criar um registro no catálogo de produtos:

  • attribute_set_id
  • sku

adicional:

  • type_id — se não especificarmos, será utilizado 'simple'

Para gravação direta no banco, utilizo o adaptador de banco de dados 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;
}

Após registrar o produto em catalog_product_entity ele se torna visível no painel, na grade de produtos (Catalog / Products).

Magento 2: importação de produtos diretamente no banco de dados

Relação entre produto e site

A relação entre o produto e o site determina em quais lojas e em quais vitrines o produto estará disponível no frontend.

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ção de produtos diretamente no banco de dados

Atributos básicos do produto

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

  • nome
  • price.
  • descrição
  • short_description
  • status
  • tax_class_id
  • url_key
  • visibility

Um atributo separado para o produto é adicionado assim (detalhes sobre a obtenção do identificador e tipo de atributo pelo seu código foram omitidos):

public function create($prodId, $attrCode, $attrValue)
{
    $attrId = /* obter ID do atributo pelo código do atributo */
    $attrType = /* obter tipo do atributo [datetime|decimal|int|text|varchar] pelo código do atributo */
    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,
            /* colocar todos os atributos na visualização de loja padrão com id=0 (admin) */
            'store_id' => 0,
            'value' => $attrValue
        ];
        $conn->insert($table, $bind);
    }
}

Pelo código do atributo, determinamos seu id e tipo de dados (datetime, decimal, int, ntext, varchar), em seguida, escrevemos os dados correspondentes na tabela para a visualização administrativa (store_id = 0).

Após adicionar os atributos listados acima ao produto, obtemos a seguinte imagem no painel:

Magento 2: importação de produtos diretamente no banco de dados

Dados de inventário

A partir da versão 2.3 do Magento, existem dois conjuntos de tabelas que garantem o armazenamento das informações de inventário (quantidade do produto):

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

Os dados de inventário devem ser inseridos em ambas as estruturas, uma vez que a nova estrutura ainda não é plenamente independente da antiga (parece que para padrão o armazém na nova estrutura está sendo utilizada a tabela cataloginventory_stock_status como um inventory_stock_1).

cataloginventory_

Ao implantar o Magento 2.3, temos inicialmente 2 registros em store_website, correspondendo a dois sites — um administrativo e um principal para o cliente:

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

Na tabela cataloginventory_stock temos apenas um registro:

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

Ou seja, na estrutura antiga, temos apenas um "armazém" (stock) e ele está vinculado ao site administrativo. A adição de novos sources/não há colunas listadas nos argumentos no MSI (nova estrutura) não resulta na criação de novos registros em cataloginventory_stock.

Os dados de inventário sobre os produtos na estrutura antiga são inicialmente registrados nas 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,
        /* usamos apenas um armazém na estrutura 'cataloginventory' por padrão */
        'stock_id' => 1,
        'qty' => $qty,
        'is_qty_decimal' => $isQtyDecimal,
        'is_in_stock' => $isInStock,
        /* o estoque padrão está vinculado ao site administrativo (veja `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,
        /* usamos apenas um armazém na estrutura 'cataloginventory' por padrão */
        'stock_id' => 1, 
        'qty' => $qty,
        'stock_status' => MagentoCatalogInventoryApiDataStockStatusInterface::STATUS_IN_STOCK,
        /* o estoque padrão está vinculado ao site administrativo (veja `cataloginventory_stock`) */
        'website_id' => 0 
    ];
    $conn->insert($table, $bind);
}

inventory_

Inicialmente, a nova estrutura para armazenar dados de inventário contém 1 "fonte» (inventory_source):

source_code|name          |enabled|description   |latitude|longitude|country_id|...|
-----------|--------------|-------|--------------|--------|---------|----------|...|
default    |Fonte Padrão  |      1|Fonte Padrão  |0.000000| 0.000000|US        |...|

e um «estoque» (inventory_stock):

stock_id|name         |
--------|-------------|
       1|Estoque Padrão|

«Fonte» representa um armazenamento físico para produtos (o registro contém coordenadas físicas e um endereço postal). «Armazém» representa 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 do qual a ligação ao canal de venda ocorre (inventory_stock_sales_channel)

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

Pela estrutura de dados, pressupõe-se a existência de diferentes tipos de canais de venda, mas por padrão, utiliza-se apenas a ligação «stock«-«website» (a ligação ao site é feita pelo código do site — base).

Uma «estoque» pode estar ligada a várias «fontes«, e uma «fonte» pode estar ligada a vários «armazéns» (relação «muitos-para-muitos»). As exceções são as fontes «padrão» «fonte” e “estoque«. Elas não podem ser redesenhadas para outras entidades (limitação a nível de código — ocorre um erro «Can not save link related to Default Source or Default Stock«). Mais detalhes sobre a estrutura MSI no Magento 2 podem ser encontrados no artigo «Sistema de gerenciamento de armazéns usando CQRS e Event Sourcing. Projeto«.

Vou usar a configuração padrão e adicionar todas as informações de inventário à fonte padrão, que está envolvida no canal de venda conectado ao site com o código base (corresponde à parte do cliente da loja — veja. 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);
}

Após adicionar os dados de inventário ao produto no admin, o resultado fica assim:

Magento 2: importação de produtos diretamente no banco de dados

Mídia

Ao adicionar manualmente imagens ao produto através do admin, as informações correspondentes são registradas nas seguintes tabelas:

  • catalog_product_entity_media_gallery: registro de mídia (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: vinculação de mídia apenas a produtos (presumivelmente, conteúdo de mídia padrão para o produto);
  • catalog_product_entity_varchar: aqui são armazenadas as funções nas quais a imagem é usada;

e as próprias imagens são armazenadas no diretório .\/pub\/media\/catalog\/product\/x\/y\/, onde x e y — as duas primeiras letras do nome do arquivo da imagem. Por exemplo, o arquivo image.png deve ser salvo como ./pub/media/catalog/product/i/m/image.png, para que a plataforma possa usá-lo como imagem ao descrever produtos do catálogo.

Estamos registrando o arquivo de mídia em .\/pub\/media\/catalog\/product\/ arquivo de mídia (o próprio processo de upload do arquivo não é abordado neste artigo):

function createMediaGallery($imgPathPrefixed)
{
    $attrId = /* obter o ID do atributo pelo código de atributo '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,
        /* 'imagem' ou 'vídeo' */
        'media_type' => 'image',
        'disabled' => false
    ];
    $conn->insert($table, $bind);
    $result = $conn->lastInsertId($table);
    return $result;
}

Ao registrar, um novo arquivo de mídia recebe um identificador.

Vinculamos o arquivo de mídia registrado ao produto correspondente para a 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,
        /* usar a visualização da loja de administração por padrão */
        'store_id' => 0,
        'entity_id' => $prodId,
        'label' => null,
        /* temos apenas uma imagem */
        'position' => 1,
        'disabled' => false
    ];
    $conn->insert($table, $bind);
}

Vinculamos o arquivo de mídia registrado ao produto correspondente sem vinculação a qualquer vitrine. Não está claro onde exatamente esses dados são usados e por que não se pode acessar os dados da tabela anterior, mas esta tabela existe e os dados são gravados nela ao adicionar uma imagem ao produto. Portanto, aqui está.

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 utilizado com diferentes funções (o código do respectivo atributo está entre parênteses):

  • Base (image)
  • Imagem Pequena (small_image)
  • Miniatura (thumbnail)
  • Imagem de Amostra (swatch_image)

A vinculação de funções ao arquivo de mídia ocorre exatamente em catalog_product_entity_varchar. O código de vinculação é semelhante ao código na seção «Atributos básicos do produto«.

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

Magento 2: importação de produtos diretamente no banco de dados

Categorias

Principais tabelas que contêm dados sobre categorias:

  • catalog_category_entity: registro de categorias;
  • catalog_category_product: relação entre produtos e categorias;
  • catalog_category_entity_*: valores dos atributos EAV;

Inicialmente, em uma aplicação Magento vazia, o registro de categorias contém 2 categorias (eu encurtei os nomes das colunas: crt — SELECT now() as created_at, cpu.* FROM (SELECT DISTINCT tags_id from cpu) base ASOF LEFT JOIN cpu USING (tags_id, 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 frente. A categoria com id=2 (Categoria Padrão) é a categoria raiz para a loja principal do site principal (Main Website Store), que é criada durante o processo de implantação da aplicação (veja. Admin \/ Stores \/ All Stores). E a própria categoria raiz da loja na frente também não está disponível, apenas suas subcategorias.

Como o tema deste artigo é a importação de dados de produtos, não usarei a gravação direta no banco ao criar categorias, mas em vez disso, utilizarei as classes fornecidas pelo próprio Magento (modelos e repositórios). A gravação direta no banco é usada apenas para relacionar o produto importado com a categoria (a correspondência da categoria é feita pelo seu nome, ao fazer a correspondência, obtemos o id da categoria):

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

Após adicionar a relação do produto com as categorias 'Categoria 1' e 'Categoria 2', os detalhes do produto no painel de administração ficam assim:

Magento 2: importação de produtos diretamente no banco de dados

Ações adicionais

Após a conclusão da importação de dados, é necessário realizar as seguintes ações adicionais:

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

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

Magento 2: importação de produtos diretamente no banco de dados

e na frente:

Magento 2: importação de produtos diretamente no banco de dados

Resumo

O mesmo conjunto de produtos (10 peças) que no artigo anterior é importado, pelo menos, de forma muito mais rápida (1 segundo contra 10). Para uma avaliação mais precisa da velocidade, seria necessário um maior número de produtos — algumas centenas, ou melhor, milhares. No entanto, mesmo com esse pequeno tamanho de entrada, é possível concluir que o uso das ferramentas fornecidas pelo Magento (modelos e repositórios) acelera significativamente (enfatizo — significativamente!) o desenvolvimento das funcionalidades requeridas, mas ao mesmo tempo reduz significativamente (enfatizo — significativamente!) a velocidade de inserção de dados no banco.

No final, a água se mostrou molhada, e isso não é nenhuma revelação. No entanto, agora eu tenho um código para experimentar mais e, talvez, fazer conclusões mais interessantes.

Fonte: habr.com

Compre hospedagem confiável para sites com proteção contra DDoS, servidores VPS VDS 🔥 Compre hospedagem confiável para sites com proteção contra DDoS, servidores VPS VDS | ProHoster