Em 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 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.

O código resultante da redação de ambos os artigos pode ser visualizado no módulo Magento "". 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_idsku
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).

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);
}
Atributos básicos do produto
Um produto recém-registrado ainda não possui nome ou descrição. Tudo isso é feito através de . Aqui está uma lista de atributos básicos do produto que são necessários para que o produto seja exibido corretamente no frontend:
nomeprice.descriçãoshort_descriptionstatustax_class_idurl_keyvisibility
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:

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_itemcataloginventory_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 ««.
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:

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.
catalog_product_entity_media_gallery
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.
catalog_product_entity_media_gallery_value
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);
}catalog_product_entity_media_gallery_value_to_entity
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:

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:

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 '«
Produtos no painel de administração após a execução das ações adicionais:

e na frente:

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
