Magento 2: nhập sản phẩm trực tiếp vào cơ sở dữ liệu

В bài viết trước Tôi đã mô tả quá trình nhập sản phẩm vào Magento 2 theo cách thông thường - thông qua các mô hình và kho lưu trữ. Phương pháp thông thường có tốc độ xử lý dữ liệu rất thấp. Máy tính xách tay của tôi sản xuất khoảng một sản phẩm mỗi giây. Trong phần tiếp theo này, tôi xem xét một cách khác để nhập sản phẩm - bằng cách nhập trực tiếp vào cơ sở dữ liệu, bỏ qua các cơ chế tiêu chuẩn của Magento 2 (mô hình, nhà máy, kho lưu trữ). Trình tự các bước để nhập sản phẩm có thể được điều chỉnh cho phù hợp với bất kỳ ngôn ngữ lập trình nào có thể hoạt động với MySQL.

Từ chối trách nhiệm: Magento có sẵn chức năng cho nhập dữ liệu và rất có thể, nó sẽ là đủ đối với bạn. Tuy nhiên, nếu bạn cần kiểm soát hoàn toàn hơn quá trình nhập, không giới hạn ở việc chuẩn bị tệp CSV cho những gì bạn có, chào mừng bạn đến với cat.

Magento 2: nhập sản phẩm trực tiếp vào cơ sở dữ liệu

Mã thu được từ việc viết cả hai bài viết có thể được xem trong mô-đun Magento "flacer32/mage2_ext_demo_import". Dưới đây là một số hạn chế mà tôi đã tuân theo để đơn giản hóa mã mô-đun demo:

  • Sản phẩm chỉ được tạo ra, không được cập nhật.
  • Một kho
  • Chỉ tên danh mục được nhập mà không có cấu trúc của chúng
  • Cấu trúc dữ liệu tuân theo phiên bản 2.3

JSON để nhập một sản phẩm:

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

Tổng quan về các giai đoạn chính của nhập khẩu

  • đăng ký sản phẩm
  • kết nối giữa sản phẩm và website
  • Thuộc tính sản phẩm cơ bản (EAV)
  • Dữ liệu tồn kho (số lượng sản phẩm trong kho)
  • phương tiện truyền thông (hình ảnh)
  • kết nối với danh mục danh mục

Đăng ký sản phẩm

Thông tin cơ bản về sản phẩm có thể được tìm thấy trong 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`)
)

Thông tin tối thiểu cần thiết để tạo một mục trong sổ đăng ký sản phẩm là:

  • attribute_set_id
  • sku

thêm vào:

  • type_id — nếu chúng ta không chỉ định nó thì 'đơn giản' sẽ được sử dụng

Để ghi trực tiếp vào cơ sở dữ liệu, tôi sử dụng bộ chuyển đổi DB của chính 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;
}

Sau khi đăng ký sản phẩm với catalog_product_entity nó sẽ hiển thị trong bảng quản trị, trong lưới sản phẩm (Danh mục/Sản phẩm).

Magento 2: nhập sản phẩm trực tiếp vào cơ sở dữ liệu

Mối liên hệ giữa sản phẩm và website

Sự liên kết của sản phẩm với trang web sẽ xác định cửa hàng và nơi trưng bày sản phẩm sẽ có mặt ở phía trước.

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: nhập sản phẩm trực tiếp vào cơ sở dữ liệu

Thuộc tính sản phẩm cơ bản

Sản phẩm mới đăng ký chưa có tên hoặc mô tả. Tất cả điều này được thực hiện thông qua Thuộc tính EAV. Dưới đây là danh sách các thuộc tính cơ bản của sản phẩm cần thiết để sản phẩm được hiển thị chính xác ở mặt trước:

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

Một thuộc tính riêng biệt được thêm vào một sản phẩm như thế này (chi tiết về việc lấy mã định danh và loại thuộc tính từ mã của nó bị bỏ qua):

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

Sử dụng mã thuộc tính, chúng tôi xác định id và kiểu dữ liệu của nó (datetime, decimal, int, text, varchar), sau đó ghi dữ liệu cho cửa sổ quản trị vào bảng thích hợp (store_id = 0).

Sau khi thêm các thuộc tính trên vào sản phẩm, bạn sẽ có được hình ảnh này trong bảng quản trị:

Magento 2: nhập sản phẩm trực tiếp vào cơ sở dữ liệu

Dữ liệu tồn kho

Bắt đầu từ phiên bản 2.3 trong Magento, có hai bộ bảng song song cung cấp khả năng lưu trữ thông tin hàng tồn kho (số lượng sản phẩm):

  • cataloginventory_*: kết cấu cũ;
  • inventory_*: cấu trúc mới (MSI - Multi Source Inventory);

Bạn cần thêm dữ liệu hàng tồn kho vào cả hai cấu trúc, bởi vì cấu trúc mới chưa hoàn toàn độc lập với cấu trúc cũ (rất có thể là đối với default kho trong cấu trúc mới có một bảng tham gia cataloginventory_stock_status như inventory_stock_1).

danh mục kiểm kê_

Khi triển khai Magneto 2.3 ban đầu chúng tôi có 2 mục trong store_website, tương ứng với hai trang web - máy khách quản trị và máy khách chính:

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

Bàn cataloginventory_stock chúng tôi chỉ có một mục:

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

Nghĩa là, trong cấu trúc cũ của chúng tôi chỉ có một “nhà kho” (stock) và nó được liên kết với trang web quản trị. Thêm cái mới thông qua bảng quản trị sources/stocks trong MSI (cấu trúc mới) không dẫn đến các mục mới trong cataloginventory_stock.

Số liệu tồn kho về sản phẩm theo cơ cấu cũ ban đầu được ghi vào các bảng:

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

hàng tồn kho_

Ban đầu, cấu trúc mới để lưu trữ dữ liệu hàng tồn kho chứa 1 "nguồn'(inventory_source):

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

và một "kho'(inventory_stock):

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

«Nguồn» thể hiện việc lưu trữ vật lý cho các sản phẩm (bản ghi chứa tọa độ vật lý và địa chỉ gửi thư). "Kho"là sự kết hợp logic của một số" nguồn "(inventory_source_stock_link)

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

ở cấp độ xảy ra kết nối với kênh bán hàng (inventory_stock_sales_channel)

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

Đánh giá theo cấu trúc dữ liệu, nhiều loại kênh bán hàng khác nhau được giả định, nhưng theo mặc định chỉ có kết nối “cổ phần"-"trang mạng"(liên kết đến trang web tuân theo mã trang web - base).

Một "kho"có thể được liên kết với một số"nguồn"và một "nguồn" - một số "kho hàng"(mối quan hệ nhiều-nhiều). Các ngoại lệ là mặc định "nguồn"Và"kho". Chúng không được liên kết lại với các thực thể khác (hạn chế ở cấp mã - lỗi “Không thể lưu liên kết liên quan đến Nguồn mặc định hoặc Kho mặc định"). Bạn có thể tìm thêm thông tin chi tiết về cấu trúc MSI trong Magento 2 trong bài viết “Hệ thống quản lý kho sử dụng CQRS và Event Sourcing. Thiết kế".

Mình sẽ sử dụng cấu hình mặc định và thêm toàn bộ thông tin kho vào nguồn default, tham gia kênh bán hàng liên kết với website bằng mã base (tương ứng với mặt trước của cửa hàng - xem 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);
}

Sau khi thêm dữ liệu hàng tồn kho vào sản phẩm trong bảng quản trị, bạn sẽ có được hình ảnh này:

Magento 2: nhập sản phẩm trực tiếp vào cơ sở dữ liệu

Phương tiện truyền thông

Khi thêm hình ảnh vào sản phẩm “thủ công” thông qua bảng quản trị, thông tin liên quan sẽ được ghi vào các bảng sau:

  • catalog_product_entity_media_gallery: đăng ký phương tiện (tệp hình ảnh và video);
  • catalog_product_entity_media_gallery_value: liên kết phương tiện truyền thông với sản phẩm và trưng bày (bản địa hóa);
  • catalog_product_entity_media_gallery_value_to_entity: chỉ liên kết phương tiện với sản phẩm (có lẽ là nội dung phương tiện mặc định cho sản phẩm);
  • catalog_product_entity_varchar: Các vai trò sử dụng hình ảnh được lưu trữ ở đây;

và bản thân hình ảnh được lưu vào thư mục ./pub/media/catalog/product/x/y/Đâu x и y — chữ cái đầu tiên và thứ hai của tên tệp hình ảnh. Ví dụ: tập tin image.png nên được lưu dưới dạng ./pub/media/catalog/product/i/m/image.png, để nền tảng có thể sử dụng nó làm hình ảnh khi mô tả các sản phẩm trong danh mục.

Đăng ký được đăng trong ./pub/media/catalog/product/ tập tin media (quá trình đặt tập tin không được thảo luận trong bài viết này):

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

Khi đăng ký, một tệp phương tiện mới sẽ được gán một mã định danh.

Chúng tôi liên kết tệp phương tiện đã đăng ký với sản phẩm tương ứng cho mặt tiền cửa hàng mặc định:

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

Chúng tôi liên kết tệp phương tiện đã đăng ký với sản phẩm tương ứng mà không bị ràng buộc với bất kỳ mặt tiền cửa hàng nào. Không rõ chính xác dữ liệu này được sử dụng ở đâu và tại sao không thể truy cập dữ liệu từ bảng trước đó, nhưng bảng này tồn tại và dữ liệu được ghi vào đó khi hình ảnh được thêm vào sản phẩm. À chính nó đấy.

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

Một tệp phương tiện có thể được sử dụng với các vai trò khác nhau (mã thuộc tính tương ứng được chỉ định trong ngoặc đơn):

  • Căn cứ(image)
  • Hình Ảnh Nhỏ (small_image)
  • Hình nhỏ (thumbnail)
  • Mẫu ảnh (swatch_image)

Liên kết các vai trò với một tệp phương tiện chính xác là những gì xảy ra trong catalog_product_entity_varchar. Mã ràng buộc tương tự như mã trong phần "Thuộc tính sản phẩm cơ bản".

Sau khi thêm hình ảnh vào sản phẩm trong bảng quản trị, nó trông như thế này:

Magento 2: nhập sản phẩm trực tiếp vào cơ sở dữ liệu

Thể loại

Các bảng chính chứa dữ liệu theo danh mục:

  • catalog_category_entity: đăng ký danh mục;
  • catalog_category_product: kết nối giữa sản phẩm và chủng loại;
  • catalog_category_entity_*: Giá trị thuộc tính EAV;

Ban đầu, trong một ứng dụng Magento trống, sổ đăng ký danh mục chứa 2 danh mục (tôi đã rút ngắn tên cột: 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|

Danh mục có id=1 là thư mục gốc của toàn bộ danh mục Magento và không có sẵn trong bảng quản trị hoặc trên trang đầu. Danh mục có id=2 (Danh mục Mặc định) là danh mục gốc cho cửa hàng chính của trang web chính (Cửa hàng trang web chính) được tạo khi ứng dụng được triển khai (xem. Quản trị / Cửa hàng / Tất cả cửa hàng). Hơn nữa, bản thân danh mục gốc của cửa hàng cũng không có ở phía trước, chỉ có các danh mục phụ của nó.

Vì chủ đề của bài viết này vẫn là nhập dữ liệu về sản phẩm nên tôi sẽ không sử dụng tính năng nhập trực tiếp vào cơ sở dữ liệu khi tạo danh mục mà sẽ sử dụng các lớp do chính Magento cung cấp (model và kho lưu trữ). Việc nhập trực tiếp vào cơ sở dữ liệu chỉ được sử dụng để liên kết sản phẩm đã nhập với một danh mục (danh mục được khớp theo tên của nó và id danh mục được truy xuất trong quá trình khớp):

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

Sau khi thêm liên kết sản phẩm vào danh mục “Danh mục 1” và “Danh mục 2”, chi tiết sản phẩm trong bảng quản trị sẽ trông giống như sau:

Magento 2: nhập sản phẩm trực tiếp vào cơ sở dữ liệu

Hành động bổ sung

Sau khi quá trình nhập dữ liệu hoàn tất, bạn cần hoàn thành các bước bổ sung sau:

  • lập chỉ mục dữ liệu: gọi trong bảng điều khiển ./bin/magento indexer:reindex;
  • tạo lại URL cho sản phẩm/danh mục: bạn có thể sử dụng tiện ích mở rộng “elgentos/tái tạo-catalog-urls«

Các sản phẩm trong bảng quản trị sau khi thực hiện các thao tác bổ sung:

Magento 2: nhập sản phẩm trực tiếp vào cơ sở dữ liệu

và ở phía trước:

Magento 2: nhập sản phẩm trực tiếp vào cơ sở dữ liệu

Tóm tắt thông tin

Cùng một bộ sản phẩm (10 chiếc) như trong bài viết trước được nhập nhanh hơn ít nhất một bậc (1 giây so với 10). Để ước tính chính xác hơn tốc độ, bạn cần số lượng sản phẩm lớn hơn - vài trăm hoặc tốt hơn là hàng nghìn. Tuy nhiên, ngay cả với kích thước dữ liệu đầu vào nhỏ như vậy, chúng ta có thể kết luận rằng việc sử dụng các công cụ do Magento (mô hình và kho lưu trữ) cung cấp là rất đáng kể (tôi nhấn mạnh - nhiều!) Tăng tốc độ phát triển các chức năng cần thiết, nhưng đồng thời cũng đáng kể (tôi nhấn mạnh - nhiều!) Giảm tốc độ dữ liệu vào cơ sở dữ liệu.

Kết quả là nước trở nên ướt và đây không phải là một điều mặc khải. Tuy nhiên, bây giờ tôi đã có mã để sử dụng và có lẽ sẽ đưa ra một số kết luận thú vị hơn.

Nguồn: www.habr.com