Magento 2: mengimpor produk langsung ke database

В Artikel sebelumnya Saya menjelaskan proses mengimpor produk ke Magento 2 dengan cara biasa - melalui model dan repositori. Cara biasa memiliki kecepatan pengolahan data yang sangat rendah. Laptop saya menghasilkan sekitar satu produk per detik. Dalam kelanjutan ini, saya mempertimbangkan cara alternatif untuk mengimpor produk - dengan masuk langsung ke database, melewati mekanisme standar Magento 2 (model, pabrik, repositori). Urutan langkah-langkah mengimpor produk dapat disesuaikan dengan bahasa pemrograman apa pun yang dapat bekerja dengan MySQL.

Penolakan tanggung jawab: Magento memiliki fungsionalitas siap pakai untuk impor data dan, kemungkinan besar, itu sudah cukup untuk Anda. Namun, jika Anda memerlukan kontrol lebih lengkap atas proses impor, tidak terbatas pada menyiapkan file CSV untuk apa yang Anda miliki, selamat datang di cat.

Magento 2: mengimpor produk langsung ke database

Kode hasil penulisan kedua artikel tersebut dapat dilihat pada modul Magento”flancer32/mage2_ext_demo_import". Berikut beberapa batasan yang saya ikuti untuk menyederhanakan kode modul demo:

  • Produk hanya dibuat, bukan diperbarui.
  • Satu gudang
  • Hanya nama kategori yang diimpor, tanpa strukturnya
  • Struktur data mematuhi versi 2.3

JSON untuk mengimpor satu produk:

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

Ikhtisar tahapan utama impor

  • pendaftaran produk itu sendiri
  • hubungan antara produk dan situs web
  • atribut produk dasar (EAV)
  • data inventaris (jumlah produk dalam stok)
  • media (gambar)
  • koneksi dengan kategori katalog

Registrasi produk

Informasi produk dasar dapat ditemukan di 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`)
)

Informasi minimum yang diperlukan untuk membuat entri di registri produk adalah:

  • attribute_set_id
  • sku

tambahan:

  • type_id — jika kita tidak menentukannya, maka 'sederhana' akan digunakan

Untuk menulis langsung ke database, saya menggunakan adaptor DB Magento itu sendiri:

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

Setelah mendaftarkan produk dengan catalog_product_entity itu menjadi terlihat di panel admin, di kisi produk (Katalog/Produk).

Magento 2: mengimpor produk langsung ke database

Hubungan antara produk dan situs web

Keterkaitan produk dengan situs menentukan di toko mana dan display produk akan tersedia di bagian depan.

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: mengimpor produk langsung ke database

Atribut produk dasar

Produk yang baru didaftarkan belum memiliki nama atau deskripsi. Semua ini dilakukan melalui Atribut EAV. Berikut daftar atribut dasar produk yang diperlukan agar produk dapat ditampilkan dengan benar di bagian depan:

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

Atribut terpisah ditambahkan ke produk seperti ini (detail untuk mendapatkan pengidentifikasi dan jenis atribut dari kodenya dihilangkan):

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

Dengan menggunakan kode atribut, kami menentukan id dan tipe datanya (datetime, decimal, int, text, varchar), lalu tulis data untuk jendela administratif ke dalam tabel yang sesuai (store_id = 0).

Setelah menambahkan atribut di atas ke produk, Anda mendapatkan gambar ini di panel admin:

Magento 2: mengimpor produk langsung ke database

Data inventaris

Mulai dari versi 2.3 di Magento, terdapat dua kumpulan tabel paralel yang menyediakan penyimpanan informasi inventaris (kuantitas produk):

  • cataloginventory_*: struktur lama;
  • inventory_*: struktur baru (MSI - Inventaris Multi Sumber);

Anda perlu menambahkan data inventaris ke kedua struktur, karena struktur baru belum sepenuhnya independen dari struktur lama (sangat mungkin terjadi default gudang dalam struktur baru sebuah meja terlibat cataloginventory_stock_status sebagai inventory_stock_1).

inventaris katalog_

Saat menerapkan Magneto 2.3 awalnya kami memiliki 2 entri store_website, yang sesuai dengan dua situs - administratif dan klien utama:

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

Meja cataloginventory_stock kami hanya memiliki satu entri:

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

Artinya, dalam struktur lama kita hanya ada satu “gudang” (stock) dan ditautkan ke situs web administratif. Menambahkan yang baru melalui panel admin sources/stocks di MSI (struktur baru) tidak menghasilkan entri baru cataloginventory_stock.

Data inventaris produk dalam struktur lama awalnya dicatat dalam tabel:

  • cataloginventory_stock_item
  • cataloginventory_stock_status

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

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

inventaris_

Awalnya, struktur baru untuk menyimpan data inventaris berisi 1"sumber'(inventory_source):

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

dan satu "gudang'(inventory_stock):

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

«Источник» mewakili penyimpanan fisik produk (catatan berisi koordinat fisik dan alamat surat). "Gudang"adalah gabungan logis dari beberapa "sumber" (inventory_source_stock_link)

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

pada tingkat di mana koneksi ke saluran penjualan terjadi (inventory_stock_sales_channel)

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

Dilihat dari struktur datanya, diasumsikan berbagai jenis saluran penjualan, tetapi secara default hanya koneksi “saham"-"situs web"(tautan ke situs web mengikuti kode situs web - base).

Satu "gudang"dapat dihubungkan ke beberapa"ke sumber"dan satu "sumber" - ke beberapa "gudang“(hubungan banyak ke banyak). Pengecualian adalah default "sumber"Dan"gudang". Mereka tidak ditautkan kembali ke entitas lain (batasan pada tingkat kode - kesalahan “Tidak dapat menyimpan link terkait Sumber Default atau Stok Default"). Detail lebih lanjut tentang struktur MSI di Magento 2 dapat ditemukan di artikel “Sistem manajemen gudang menggunakan CQRS dan Event Sourcing. Desain".

Saya akan menggunakan konfigurasi default dan menambahkan semua informasi inventaris ke sumbernya default, yang terlibat dalam saluran penjualan yang terkait dengan situs web dengan kode tersebut base (sesuai dengan bagian depan toko - lihat 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);
}

Setelah menambahkan data inventaris produk di panel admin, Anda mendapatkan gambar ini:

Magento 2: mengimpor produk langsung ke database

Media

Saat menambahkan gambar ke produk secara “manual” melalui panel admin, informasi yang relevan dicatat dalam tabel berikut:

  • catalog_product_entity_media_gallery: registri media (file gambar dan video);
  • catalog_product_entity_media_gallery_value: menghubungkan media dengan produk dan etalase (lokalisasi);
  • catalog_product_entity_media_gallery_value_to_entity: menghubungkan media ke produk saja (mungkin konten media default untuk produk);
  • catalog_product_entity_varchar: Peran di mana gambar tersebut digunakan disimpan di sini;

dan gambarnya sendiri disimpan ke direktori ./pub/media/catalog/product/x/y/Dimana x и y — huruf pertama dan kedua dari nama file gambar. Misalnya, berkas image.png harus disimpan sebagai ./pub/media/catalog/product/i/m/image.png, sehingga platform dapat menggunakannya sebagai gambar saat mendeskripsikan produk dari katalog.

Daftar diposting di ./pub/media/catalog/product/ file media (proses penempatan file itu sendiri tidak dibahas dalam artikel ini):

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

Saat didaftarkan, file media baru diberi pengidentifikasi.

Kami mengaitkan file media terdaftar dengan produk yang sesuai untuk etalase default:

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

Kami mengaitkan file media terdaftar dengan produk terkait tanpa terikat ke etalase mana pun. Tidak jelas di mana tepatnya data ini digunakan dan mengapa data dari tabel sebelumnya tidak dapat diakses, tetapi tabel ini ada dan data ditulis ke dalamnya ketika gambar ditambahkan ke produk. Jadi itu saja.

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

katalog_produk_entitas_varchar

File media dapat digunakan dengan peran berbeda (kode atribut terkait ditunjukkan dalam tanda kurung):

  • Basis(image)
  • Gambar Kecil (small_image)
  • Gambar kecil (thumbnail)
  • Contoh Gambar (swatch_image)

Menautkan peran ke file media adalah hal yang persis terjadi catalog_product_entity_varchar. Kode pengikatannya mirip dengan kode di "Atribut produk dasar".

Setelah menambahkan gambar ke produk di panel admin tampilannya seperti ini:

Magento 2: mengimpor produk langsung ke database

Kategori

Tabel utama berisi data berdasarkan kategori:

  • catalog_category_entity: daftar kategori;
  • catalog_category_product: hubungan antara produk dan kategori;
  • catalog_category_entity_*: nilai atribut EAV;

Awalnya pada aplikasi Magento yang kosong, registry kategori berisi 2 kategori (saya persingkat nama kolomnya: 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|

Kategori dengan id=1 adalah akar dari keseluruhan katalog Magento dan tidak tersedia di panel admin atau di halaman depan. Kategori dengan id=2 (Default Kategori) adalah kategori akar untuk penyimpanan utama situs utama (Toko Situs Web Utama) dibuat saat aplikasi dikerahkan (lihat. Admin / Toko / Semua Toko). Selain itu, kategori root dari toko itu sendiri juga tidak tersedia di bagian depan, hanya subkategorinya saja.

Karena topik artikel ini masih mengimpor data produk, saya tidak akan menggunakan entri langsung ke database saat membuat kategori, tetapi akan menggunakan kelas yang disediakan oleh Magento sendiri (model dan repositori). Entri langsung ke database hanya digunakan untuk mengaitkan produk yang diimpor dengan suatu kategori (kategori dicocokkan berdasarkan namanya, dan id kategori diambil selama pencocokan):

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

Setelah menambahkan link produk ke kategori “Kategori 1” dan “Kategori 2”, detail produk di panel admin terlihat seperti ini:

Magento 2: mengimpor produk langsung ke database

Tindakan tambahan

Setelah impor data selesai, Anda perlu menyelesaikan langkah tambahan berikut:

  • pengindeksan data: panggilan di konsol ./bin/magento indexer:reindex;
  • membuat ulang URL untuk produk/kategori: Anda dapat menggunakan ekstensi “elgentos/regenerasi-katalog-url«

Produk di panel admin setelah melakukan tindakan tambahan:

Magento 2: mengimpor produk langsung ke database

dan di depan:

Magento 2: mengimpor produk langsung ke database

Ringkasan

Kumpulan produk yang sama (10 buah) seperti pada artikel sebelumnya diimpor setidaknya dengan urutan besarnya lebih cepat (1 detik versus 10). Untuk memperkirakan kecepatan dengan lebih akurat, Anda memerlukan jumlah produk yang lebih banyak - beberapa ratus, atau bahkan ribuan. Namun, bahkan dengan ukuran data masukan yang kecil, kita dapat menyimpulkan bahwa penggunaan alat yang disediakan oleh Magento (model dan repositori) adalah signifikan (saya tekankan - banyak!) mempercepat pengembangan fungsionalitas yang diperlukan, tetapi pada saat yang sama secara signifikan (saya tekankan - banyak!) mengurangi kecepatan masuknya data ke database.

Akibatnya air menjadi basah dan ini bukan wahyu. Namun, sekarang saya memiliki kode untuk dimainkan dan mungkin sampai pada beberapa kesimpulan yang lebih menarik.

Sumber: www.habr.com