在上一篇文章中,我描述了通过模型和存储库以通常的方式将产品导入Magento 2的过程。 通常的方法的特征在于非常低的数据处理速度。 每秒大约有一种产品出现在我的笔记本电脑上。 在接下来的内容中,我考虑了一种导入产品的替代方法-通过直接记录到数据库,绕过Magento 2的标准机制(模型,工厂,存储库)。 导入产品的步骤顺序可以适应于可以与MySQL一起使用的任何编程语言。
免责声明 :Magento具有用于导入数据的现成功能,很可能您已经足够了。 但是,如果您需要对导入过程进行更全面的控制,不仅限于准备一个CSV文件,欢迎您。

撰写这两篇文章所产生的代码可以在flancer32 / mage2_ext_demo_import Magento模块中查看。 以下是我为简化演示模块代码而遵循的一些限制:
- 仅创建产品,不更新产品。
- 一个仓库
- 仅导入类别名称,不导入其结构
- 数据结构符合2.3版
JSON导入单个产品:
{ "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" }
主要导入步骤概述
- 产品注册
- 链接产品和网站
- 基本产品属性(EAV)
- 库存数据(库存产品的数量)
- 媒体(图片)
- 链接到目录类别
产品注册
基本产品信息在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`) )
在产品注册表中创建条目所需的最少信息:
附加:
type_id
如果未type_id
,则将使用'simple'
为了直接记录到数据库,我使用了Magento本身的数据库适配器:
function create($sku, $typeId, $attrSetId) { $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; }
在catalog_product_entity
注册产品后,该产品将在管理面板中的产品网格( Catalog / Products )中可见。

链接产品和网站
产品与站点之间的关系确定了产品将在前面的哪些商店和展示柜中提供。
function linkToWebsite($prodId, $websiteId) { $conn = $this->resource->getConnection(); $table = $this->resource->getTableName('catalog_product_website'); $bind = [ 'product_id' => $prodId, 'website_id' => $websiteId ]; $conn->insert($table, $bind); }

基本产品属性
新注册的产品还没有名称或描述。 所有这些都是通过EAV属性完成的。 这是确保产品正确显示在前面所需的基本产品属性列表:
name
price
description
short_description
status
tax_class_id
url_key
visibility
像这样向产品添加一个单独的属性(省略了通过其代码获取标识符和属性类型的详细信息):
public function create($prodId, $attrCode, $attrValue) { $attrId = $attrType = if ($attrId) { $conn = $this->resource->getConnection(); $tblName = 'catalog_product_entity_' . $attrType; $table = $this->resource->getTableName($tblName); $bind = [ 'attribute_id' => $attrId, 'entity_id' => $prodId, 'store_id' => 0, 'value' => $attrValue ]; $conn->insert($table, $bind); } }
使用属性代码,确定其ID和数据类型( datetime
, decimal
, int
, text
, varchar
),然后在相应的表中写入管理店面的数据( store_id = 0
)。
在将上述属性添加到产品中后,我们在管理面板中得到了这张图片:

库存数据
从2.3版开始,Magento同时具有两组表,用于存储库存信息(产品数量):
cataloginventory_*
:旧结构;inventory_*
_ inventory_*
:新结构(MSI-多源库存);
您需要将库存数据添加到两个结构中,因为 新结构尚未完全独立于旧结构(看起来, cataloginventory_stock_status
表已cataloginventory_stock_status
新结构中default
仓库的stock_stock_1)。
cataloginventory_
在部署Magneto 2.3时,我们最初在store_website
有2个条目,它们对应于两个站点-管理站点和主要客户端:
website_id|code |name |sort_order|default_group_id|is_default| ----------|-----|------------|----------|----------------|----------| 0|admin|Admin | 0| 0| 0| 1|base |Main Website| 0| 1| 1|
在cataloginventory_stock
表中,我们只有一个条目:
stock_id|website_id|stock_name| --------|----------|----------| 1| 0|Default |
也就是说,在我们的旧结构中,只有一个“仓库”( stock
),并且它与管理网站绑定在一起。 通过管理stocks
(新结构)向MSI添加新sources
/ stocks
不会在cataloginventory_stock
导致新条目。
表中最初写有旧结构产品的库存数据:
cataloginventory_stock_item
cataloginventory_stock_status
cataloginventory_stock_item
function createOldItem($prodId, $qty) { $isQtyDecimal = (((int)$qty) != $qty); $isInStock = ($qty > 0); $conn = $this->resource->getConnection(); $table = $this->resource->getTableName('cataloginventory_stock_item'); $bind = [ 'product_id' => $prodId, 'stock_id' => 1, 'qty' => $qty, 'is_qty_decimal' => $isQtyDecimal, 'is_in_stock' => $isInStock, 'website_id' => 0 ]; $conn->insert($table, $bind); }
cataloginventory_stock_status
function createOldStatus($prodId, $qty) { $isInStock = ($qty > 0); $conn = $this->resource->getConnection(); $table = $this->resource->getTableName('cataloginventory_stock_status'); $bind = [ 'product_id' => $prodId, 'stock_id' => 1, 'qty' => $qty, 'stock_status' => \Magento\CatalogInventory\Api\Data\StockStatusInterface::STATUS_IN_STOCK, 'website_id' => 0 ]; $conn->insert($table, $bind); }
库存_
最初,用于存储库存数据的新结构包含1个“ 源 ”( inventory_source
):
source_code|name |enabled|description |latitude|longitude|country_id|...| -----------|--------------|-------|--------------|--------|---------|----------|...| default |Default Source| 1|Default Source|0.000000| 0.000000|US |...|
还有一个“ 仓库 ”( inventory_stock
):
stock_id|name | --------|-------------| 1|Default Stock|
“ 来源 ”是产品的物理存储(记录包含物理坐标和邮寄地址)。 “ 仓库 ”是多个“来源”的逻辑联合( inventory_source_stock_link
)
link_id|stock_id|source_code|priority| -------|--------|-----------|--------| 1| 1|default | 1|
在该级别上有一个到销售渠道的链接( inventory_stock_sales_channel
)
type |code|stock_id| -------|----|--------| website|base| 1|
从数据结构来看,假定使用各种类型的销售渠道,但默认情况下仅使用“ 股票 ”-“ 网站 ”连接(网站链接由网站代码base
)。
一个“ 仓库 ”可以绑定到多个“ 来源 ”,一个“ 源 ”可以链接到多个“ 仓库 ”(多对多关系)。 默认为'ovye“ source ”和“ Warehouse”。 它们不绑定到其他实体(在代码级别的限制-错误“ 无法保存与默认来源或默认库存相关的链接 ”崩溃)。 您可以在Magento 2的文章“ 使用CQRS和事件源的仓库管理系统。设计 ”中阅读有关MSI结构的更多信息。
我将使用默认配置,并将所有库存信息添加到default
源,该default
源在与具有base
代码的网站关联的销售渠道中使用(对应于商店的客户端部分-请参见store_website
):
function createNewItem($sku, $qty) { $conn = $this->resource->getConnection(); $table = $this->resource->getTableName('inventory_source_item'); $bind = [ 'source_code' => 'default', 'sku' => $sku, 'quantity' => $qty, 'status' => \Magento\InventoryApi\Api\Data\SourceItemInterface::STATUS_IN_STOCK ]; $conn->insert($table, $bind); }
将库存数据添加到管理面板中的产品后,我们得到以下图片:

通过管理面板“手动”向产品添加图像时,相关信息记录在下表中:
catalog_product_entity_media_gallery
:媒体注册表(图像和视频文件);catalog_product_entity_media_gallery_value
:将媒体链接到产品和店面(本地化);catalog_product_entity_media_gallery_value_to_entity
:仅将媒体绑定到产品(可能是产品的默认媒体内容);catalog_product_entity_varchar
:使用图像的角色保存在这里;
并且图像本身保存在./pub/media/catalog/product/x/y/
目录中,其中x
和y
是图像文件名的第一个和第二个字母。 例如,必须将image.png
文件另存为./pub/media/catalog/product/i/m/image.png
以便平台在描述目录中的产品时可以将其用作图像。
我们注册位于./pub/media/catalog/product/
的媒体文件(不考虑将文件放入本文的过程):
function createMediaGallery($imgPathPrefixed) { $attrId = $conn = $this->resource->getConnection(); $table = $this->resource->getTableName('catalog_product_entity_media_gallery'); $bind = [ 'attribute_id' => $attrId, 'value' => $imgPathPrefixed, 'media_type' => 'image', 'disabled' => false ]; $conn->insert($table, $bind); $result = $conn->lastInsertId($table); return $result; }
注册时,将为新的媒体文件分配一个标识符。
我们将注册的媒体文件与相应的产品链接为默认展示柜:
function createGalleryValue($mediaId, $prodId) { $conn = $this->resource->getConnection(); $table = $this->resource->getTableName('catalog_product_entity_media_gallery_value'); $bind = [ 'value_id' => $mediaId, 'store_id' => 0, 'entity_id' => $prodId, 'label' => null, 'position' => 1, 'disabled' => false ]; $conn->insert($table, $bind); }
我们将注册的媒体文件与相应的产品相关联,而无需参考任何店面。 目前尚不清楚确切地在何处使用此数据,以及为什么无法访问上一个表的数据,但尚不清楚该表是否存在,并且在向产品添加图片时会将数据写入其中。 因此,像这样。
function createGalleryValueToEntity($mediaId, $prodId) { $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
媒体文件可以以不同的角色使用(相应属性的代码在方括号中指示):
- 底座(
image
) - 小图片(
small_image
) - 缩略图(
thumbnail
) swatch_image
图像( swatch_image
)
将角色绑定到媒体文件仅发生在catalog_product_entity_varchar
。 绑定代码类似于“ 基本产品属性 ”部分中的代码。
将图像添加到管理面板中的产品后,结果如下:

分类目录
按类别包含数据的主表:
catalog_category_entity
:类别寄存器;catalog_category_product
:产品和类别的关联;catalog_category_entity_*
:EAV属性的值;
最初,在一个空的Magento应用程序中,类别注册表包含2个类别(我缩短了列名称: 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|
id = 1的类别是整个Magento目录的根,在管理面板或前面均不可用。 id = 2( 默认类别 )的类别是在部署应用程序时创建的主网站( Main Website Store )的主商店的根类别(请参阅Admin / Stores / All Stores )。 此外,前面的商店的根目录类别也不可用,仅是其子类别。
由于本文的主题仍在导入产品数据,因此在创建类别时,我不会在数据库中使用直接记录,而是使用Magento本身提供的类(模型和存储库)。 直接记录到数据库仅用于将导入的产品与类别链接(类别通过其名称映射,在匹配时提取类别ID):
function create($prodId, $catId) { $conn = $this->resource->getConnection(); $table = $this->resource->getTableName('catalog_category_product'); $bind = [ 'category_id' => $catId, 'product_id' => $prodId, ]; $conn->insert($table, $bind); }
在将产品链接添加到类别“类别1”和“类别2”之后,管理面板中的产品详细信息如下所示:

其他动作
数据导入完成后,必须执行以下附加步骤:
完成其他步骤后,管理面板中的产品:

在前面:

总结
与上一篇文章相同的一组产品(10件)的进口速度至少快一个数量级(1秒对10)。 为了更准确地估算速度,您需要数量更多的产品-数百种,最好是数千种。 尽管如此,即使输入的数据量如此之小,也可以得出结论:使用Magento提供的工具(模型和存储库)可以显着(强调- 显着 !)加速了所需功能的开发,但是显着(强调- 显着 !)减少了数据进入数据库的速度。
结果,水被证明是湿的,这不是启示。 但是,我现在有一些代码可以使用,并可能得出更有趣的结论。