În Am descris procesul de import al produselor în Magento 2 în mod obișnuit — prin intermediul modelelor și repozitoriilor. Metoda obișnuită se distinge printr-o viteză de procesare a datelor foarte scăzută. Pe laptopul meu se verifica aproximativ un produs pe secundă. În această continuare, analizez o metodă alternativă de importare a produsului — prin scriere directă în baza de date, ocolind mecanismele standard ale Magento 2 (modele, fabrici, repozitorii). Secvența pașilor care asigură importul produselor poate fi adaptată pentru orice limbaj de programare capabil să lucreze cu MySQL.
Declinarea: În Magento există funcționalitate gata pregătită pentru și, cel mai probabil, vă va fi suficient. Totuși, dacă aveți nevoie de un control mai complet asupra procesului de import, care să nu se limiteze la pregătirea fișierului CSV pentru ceea ce există — bine ați venit sub linie.

Codul rezultat în urma scrierii ambelor articole poate fi consultat în modulul Magento „„. Iată câteva limitări la care m-am conformat pentru a simplifica codul modulului demo:
- Produsele sunt doar create, nu actualizate.
- Un singur depozit
- Se importă doar numele categoriilor, fără structura acestora
- Structurile de date corespund versiunii 2.3
JSON pentru importul unui produs individual:
{
"sku": "MVA20D-UBV-3",
"name": "Capac de sigilare VA47-29 IEK",
"desc": "Asigurarea accesului la dispozitive ...",
"desc_short": "Capacul de sigilare VA47-29 IEK este destinat pentru ...",
"price": 5.00,
"qty": 25,
"categories": ["Categoria 1", "Categoria 2"],
"image_path": "mva20d_ubv_3.png"
}Prezentarea principalelor etape de import
- înregistrarea produsului propriu-zis
- legătura dintre produs și site-ul web
- atributele de bază ale produsului (EAV)
- datele de inventar (cantitatea produsului în depozit)
- media (imagini)
- legătura cu categoriile din catalog
Înregistrarea produsului
Informațiile de bază despre produs se află în catalog_product_entity:
CREATE TABLE `catalog_product_entity` (
`entity_id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'Id-ul entității',
`attribute_set_id` smallint(5) unsigned NOT NULL DEFAULT '0' COMMENT 'Id-ul setului de atribute',
`type_id` varchar(32) NOT NULL DEFAULT 'simple' COMMENT 'Id-ul tipului',
`sku` varchar(64) DEFAULT NULL COMMENT 'SKU',
`has_options` smallint(6) NOT NULL DEFAULT '0' COMMENT 'Are opțiuni',
`required_options` smallint(5) unsigned NOT NULL DEFAULT '0' COMMENT 'Opțiuni necesare',
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Ora creării',
`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'Ora actualizării',
PRIMARY KEY (`entity_id`),
KEY `CATALOG_PRODUCT_ENTITY_ATTRIBUTE_SET_ID` (`attribute_set_id`),
KEY `CATALOG_PRODUCT_ENTITY_SKU` (`sku`)
)Informația minimă necesară pentru a crea o înregistrare în registrul produselor:
attribute_set_idsku
complementar:
type_id— dacă nu este specificat, se va folosi ‘simple’
Pentru a scrie direct în baza de date folosesc adaptorul DB al 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;
}După înregistrarea produsului în catalog_product_entity devine vizibil în admin, în grila de produse (Catalog / Produse).

Legătura dintre produs și site-ul web
Legătura dintre produs și site determină în care magazine și pe ce vitrine produsul va fi disponibil pe frontend.
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);
}
Atributele de bază ale produsului
Un produs proaspăt înregistrat nu are încă nume sau descriere. Toate acestea se fac prin . Iată lista atributelor de bază ale produsului, care sunt necesare pentru ca produsul să fie afișat corect pe frontend:
namepricedescriptionshort_descriptionstatustax_class_idurl_keyvisibility
Un atribut separat este adăugat produsului astfel (detaliile obținerii identificatorului și tipului atributului după cod sunt omise):
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);
}
}După codul atributului determinăm id-ul și tipul de date (datetime, decimal, int, text, varchar), apoi scriem datele în tabela corespunzătoare pentru vitrina administrativă (store_id = 0).
După adăugarea atributelor menționate mai sus la produs, acesta arată astfel în admin:

Informații despre inventar
Începând cu versiunea 2.3 a Magento, există în paralel două seturi de tabele care asigură stocarea informațiilor despre inventar (cantitatea produsului):
cataloginventory_*: structura veche;inventory_*: structura nouă (MSI — Multi Source Inventory);
Datele de inventar trebuie adăugate în ambele structuri, deoarece noua structură nu este încă complet independentă de vechea structură (pare că pentru default depozitul din noua structură este folosită tabela cataloginventory_stock_status ca modul inventory_stock_1).
cataloginventory_
La desfășurarea Magneto 2.3 avem inițial 2 înregistrări în store_website, care corespund celor două site-uri — administrativ și principalul client:
website_id|code |name |sort_order|default_group_id|is_default|
----------|-----|------------|----------|----------------|----------|
0|admin|Admin | 0| 0| 0|
1|base |Main Website| 0| 1| 1|În tabela cataloginventory_stock avem doar o singură înregistrare:
stock_id|website_id|stock_name|
--------|----------|----------|
1| 0|Default |Adică, avem în structura veche doar un singur „depozit” (stock) și acesta este legat de website-ul administrativ. Adăugarea de noi sources/stocks în MSI (noua structură) nu conduce la apariția de noi înregistrări în cataloginventory_stock.
Informațiile despre produse din structura veche sunt inițial scrise în tabelele:
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,
/* folosim un singur depozit în structura 'cataloginventory' în mod implicit */
'stock_id' => 1,
'qty' => $qty,
'is_qty_decimal' => $isQtyDecimal,
'is_in_stock' => $isInStock,
/* depozitul implicit este legat de website-ul administrativ (vezi `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,
/* folosim un singur depozit în structura 'cataloginventory' în mod implicit */
'stock_id' => 1,
'qty' => $qty,
'stock_status' => MagentoCatalogInventoryApiDataStockStatusInterface::STATUS_IN_STOCK,
/* depozitul implicit este legat de website-ul administrativ (vezi `cataloginventory_stock`) */
'website_id' => 0
];
$conn->insert($table, $bind);
}inventory_
Inițial, noua structură pentru stocarea datelor despre inventar conține 1 „sursa» (inventory_source):
source_code|name |enabled|description |latitude|longitude|country_id|...|
-----------|--------------|-------|--------------|--------|---------|----------|...|
default |Default Source| 1|Default Source|0.000000| 0.000000|US |...|și unul «depozit» (inventory_stock):
stock_id|name |
--------|-------------|
1|Default Stock|«Sursa» reprezintă un depozit fizic pentru produse (înregistrarea conține coordonate fizice și o adresă poștală). «Depozit» reprezintă o unitate logică formată din mai multe „surse” (inventory_source_stock_link)
link_id|stock_id|source_code|priority|
-------|--------|-----------|--------|
1| 1|default | 1|la nivelul căruia se face legătura cu canalul de vânzare (inventory_stock_sales_channel)
type |code|stock_id|
-------|----|--------|
website|base| 1|Având în vedere structura datelor, se presupun mai multe tipuri de canale de vânzare, dar implicit este folosit doar legătura „stock«-«website» (legătura către web-site se face prin codul web-site-ului — base).
Un „depozit» poate fi legat de mai multe „surse«, iar un „sursa» — la mai multe „depozite» (relația „mulți-la-mulți”). Excepțiile sunt „sursa” și „depozit«. Ele nu sunt legate la alte entități (limitare la nivel de cod — apare eroarea „Can not save link related to Default Source or Default Stock«). Mai multe detalii despre structura MSI în Magento 2 pot fi citite în articolul „«.
Voi folosi configurația default și voi adăuga toate informațiile despre inventar în sursa default, care este implicată în canalul de vânzare legat de web-site-ul cu codul base (corespunde părții clientului a magazinului — vezi. 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);
}După adăugarea datelor despre inventar la produs în admin, se obține următoarea imagine:

Media
La adăugarea „manuală” a imaginilor la produs prin admin, informațiile corespunzătoare sunt înregistrate în următoarele tabele:
catalog_product_entity_media_gallery: registrul media (imagini și fișiere video);catalog_product_entity_media_gallery_value: legătura media la produse și vitrine (localizare);catalog_product_entity_media_gallery_value_to_entity: legătura media doar la produse (presupus, conținut media default pentru produs);catalog_product_entity_varchar: aici se păstrează rolurile în care este utilizată imaginea;
iar imaginile în sine sunt salvate în catalog .\/pub\/media\/catalog\/product\/x\/y\/, unde x și Stabiliți o parolă și păstrați-o în siguranță! — primele două litere ale numelui fișierului cu imaginea. De exemplu, fișierul image.png trebuie să fie salvat ca ./pub/media/catalog/product/i/m/image.png, astfel încât platforma să poată folosi acest lucru ca imagine în descrierea produselor din catalog.
catalog_product_entity_media_gallery
Înregistrăm media plasat în .\/pub\/media\/catalog\/product\/ fișier media (procesul de plasare a fișierului în această articol nu este discutat):
function createMediaGallery($imgPathPrefixed)
{
$attrId = /* obține ID-ul atributului prin codul atributului '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' sau 'video' */
'media_type' => 'image',
'disabled' => false
];
$conn->insert($table, $bind);
$result = $conn->lastInsertId($table);
return $result;
}Atunci când se înregistrează un nou fișier media, i se atribuie un identificator.
catalog_product_entity_media_gallery_value
Legăm fișierul media înregistrat de produsul corespunzător pentru vitrina 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,
/* folosiți vizualizarea magazinului admin în mod implicit */
'store_id' => 0,
'entity_id' => $prodId,
'label' => null,
/* avem doar o singură imagine */
'position' => 1,
'disabled' => false
];
$conn->insert($table, $bind);
}catalog_product_entity_media_gallery_value_to_entity
Legăm fișierul media înregistrat de produsul corespunzător fără a-l asocia cu vreo vitrină. Nu este clar unde sunt folosite aceste date și de ce nu se poate accesa datele din tabelul anterior, dar acest tabel există, iar datele sunt înregistrate în acesta când se adaugă o imagine la produs. Așadar, așa funcționează.
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
Fișierul media poate fi utilizat cu diferite roluri (codul atributului corespunzător este specificat în paranteză):
- Base (
imagine) - Small Image (
small_image) - Thumbnail (
thumbnail) - Swatch Image (
swatch_image)
Asocierea rolurilor cu fișierul media se face exact în catalog_product_entity_varchar. Codul asocierii este similar cu cel din secțiunea «Atributele de bază ale produsului«.
După adăugarea imaginii la produs în panoul de administrare, arată cam așa:

Categorii
Tabelele principale în care sunt stocate datele pentru categorii:
catalog_category_entity: registrul categoriilor;catalog_category_product: legătura dintre produse și categorii;catalog_category_entity_*: valorile atributelor EAV;
Inițial, într-o aplicație Magento goală, registrul categoriilor conține 2 categorii (am scurtat titlurile coloanelor: 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|Categoria cu id=1 este rădăcina întregului catalog Magento și nu este disponibilă nici în panoul de administrare, nici pe front-end. Categoria cu id=2 (Default Category) este categoria principală pentru magazinul principal al site-ului principal (Main Website Store), creată la desfășurarea aplicației (vezi. Admin / Stores / All Stores). De asemenea, categoria principală a magazinului pe front-end nu este accesibilă, doar subcategoriile sale.
Deoarece subiectul acestui articol este, totuși, importul de date pentru produse, nu voi folosi scrierea directă în baza de date la crearea categoriilor, ci voi folosi clasele oferite de Magento (modele și repozitorii). Scrierea directă în baza de date este utilizată doar pentru a asocia produsul importat cu categoria (asocierea se face după numele categoriei, iar în asociere se extrage id-ul categoriei):
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);
}După adăugarea legăturii produsului cu categoriile „Categoria 1” și „Categoria 2”, detaliile produsului din panoul de administrare arată cam așa:

Acțiuni suplimentare
După finalizarea importului de date, trebuie să efectuezi următoarele acțiuni suplimentare:
- indexarea datelor: apel în consolă
./bin/magento indexer:reindex; - regenerarea URL-urilor pentru produse/categorii: poate fi folosită extensia „«
Produsele în panoul de administrare după executarea acțiunilor suplimentare:

și pe front-end:

Rezumat
Același set de produse (10 bucăți), ca și în articolul precedent, este importat, cel puțin, cu un ordin mai rapid (1 secundă față de 10). Pentru o evaluare mai precisă a vitezei, este nevoie de un număr mai mare de produse — câteva sute, de preferat mii. Cu toate acestea, chiar și cu o asemenea dimensiune mică a datelor de intrare, se poate concluziona că utilizarea uneltelor furnizate de Magento (modelele și repozitoarele) accelerează semnificativ (subliniez — semnificativ!) dezvoltarea funcționalității necesare, dar, în același timp, reduce semnificativ (subliniez — semnificativ!) viteza cu care datele ajung în bază de date.
În final, apa s-a dovedit a fi udă și nu este o revelație. Totuși, acum am codul cu care să mă joc mai departe și, poate, să fac concluzii mai interesante.
Sursa: habr.com
