V Opisao sam proces uvoza proizvoda u Magento 2 na uobičajen način — putem modela i repozitorija. Uobičajeni način se razlikuje veoma niskom brzinom obrade podataka. Na mom laptopu se postiglo otprilike jedan proizvod u sekundi. U ovom nastavku razmatram alternativni način uvoza proizvoda — direktnim unosom u bazu, mimo standardnih mehanizama Magento 2 (modeli, fabrike, repozitoriji). Redosled koraka koji omogućava uvoz proizvoda može se prilagoditi bilo kojem programskom jeziku koji može raditi sa MySQL-om.
Odricanje od odgovornosti: Magento ima gotovu funkcionalnost za i verovatno će vam to biti dovoljno. Međutim, ako vam je potrebna veća kontrola nad procesom uvoza, koja se ne ograničava samo na pripremu CSV datoteke, dobrodošli ste u nastavak.

Kod koji je rezultat pisanja oba članka može se pogledati u Magento modulu ««. Evo nekih ograničenja kojih sam se pridržavao da bih pojednostavio kod demo modula:
- Proizvodi se samo kreiraju, ne ažuriraju.
- Jedan skladište
- Uvoze se samo nazivi kategorija, bez njihove strukture.
- Strukture podataka odgovaraju verziji 2.3
JSON za uvoz pojedinačnog proizvoda:
{
"sku": "MVA20D-UBV-3",
"name": "Zagušnica za plombiranje VA47-29 IEK",
"desc": "Obezbeđivanje pristupa uređajima ...",
"desc_short": "Zagušnica za plombiranje VA47-29 IEK je namenjena za ...",
"price": 5.00,
"qty": 25,
"categories": ["Kategorija 1", "Kategorija 2"],
"image_path": "mva20d_ubv_3.png"
}Pregled osnovnih faza uvoza
- registracija samog proizvoda
- veza proizvoda i web sajta
- osnovni atributi proizvoda (EAV)
- inventarske podatke (količina proizvoda na skladištu)
- medija (slike)
- veza sa kategorijama kataloga
Registracija proizvoda
Osnovne informacije o proizvodu nalaze se u 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`)
)Minimalno potrebne informacije za kreiranje zapisa u registru proizvoda:
attribute_set_idsku
дополнительная:
type_id— если не укажем, то будет использовано ‘simple’
Za direktan zapis u bazu koristim DB-adapter same 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;
}Nakon registracije proizvoda u catalog_product_entity postaje vidljiv u administraciji, u grid-u proizvoda (Katalog / Proizvodi).

Veza proizvoda i web-sajta
Veza proizvoda sa sajtom definiše u kojim prodavnicama i na kojim izlozima će proizvod biti dostupan na frontu.
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);
}
Osnovni atributi proizvoda
Sveže registrovani proizvod još uvek nema ni naziv ni opis. Sve to se radi putem . Evo liste osnovnih atributa proizvoda koji su neophodni da bi proizvod bio dovoljno ispravno prikazan na frontu:
namecenaopiskratak_opisstatusid_porezaurl_ključevividljivost
Poseban atribut proizvoda dodaje se ovako (izostavljeni detalji o dobijanju identifikatora i tipa atributa prema njegovom kodu):
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);
}
}Prema kodu atributa određujemo njegov id i tip podataka (datetime, decimal, int, text, varchar), zatim u odgovarajuću tabelu upisujemo podatke za administrativni izlog (store_id = 0).
Nakon dodavanja prethodno navedenih atributa proizvod dobija ovakav izgled u administraciji:

Inventarni podaci
Počev od verzije 2.3 u Magento paralelno postoji dva skupa tabela koje obezbeđuju čuvanje inventarne informacije (količina proizvoda):
cataloginventory_*: stara struktura;inventarij_*: nova struktura (MSI – Multi Source Inventory);
Dodajte inventarijske podatke u obje strukture, jer nova struktura još uvijek nije potpuno neovisna o staroj (čini se da za default sklad u novoj strukturi koristi se tabela cataloginventory_stock_status в качестве inventory_stock_1).
cataloginventory_
Prilikom instalacije Magneta 2.3, inicijalno imamo 2 zapisa u store_website, što odgovara dvjema web stranicama – administrativnoj i glavnoj klijentskoj:
website_id|code |name |sort_order|default_group_id|is_default|
----------|-----|------------|----------|----------------|----------|
0|admin|Admin | 0| 0| 0|
1|base |Glavna web stranica| 0| 1| 1|U tabeli cataloginventory_stock imamo samo jedan zapis:
stock_id|website_id|stock_name|
--------|----------|----------|
1| 0|Default |Dakle, u staroj strukturi imamo samo jedan „sklad“ (stock) i on je vezan za administrativnu web stranicu. Dodavanje novih viri/stocks u MSI (novu strukturu) ne dovodi do pojave novih zapisa u cataloginventory_stock.
Inventarijski podaci o proizvodima u staroj strukturi inicijalno se upisuju u tabele:
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,
\/* koristimo samo jedan sklad u 'cataloginventory' strukturi po defaultu *\/
'stock_id' => 1,
'qty' => $qty,
'is_qty_decimal' => $isQtyDecimal,
'is_in_stock' => $isInStock,
\/* default sklad je vezan za administrativnu web stranicu (vidi `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,
\/* koristimo samo jedan sklad u 'cataloginventory' strukturi po defaultu *\/
'stock_id' => 1,
'qty' => $qty,
'stock_status' => MagentoCatalogInventoryApiDataStockStatusInterface::STATUS_IN_STOCK,
\/* default sklad je vezan za administrativnu web stranicu (vidi `cataloginventory_stock`) *\/
'website_id' => 0
];
$conn->insert($table, $bind);
}inventarij_
Inicijalno, nova struktura za pohranu inventarijskih podataka sadrži 1 „vir» (inventory_source):
source_code|name |enabled|description |latitude|longitude|country_id|...|
-----------|--------------|-------|--------------|--------|---------|----------|...|
default |Izvor po defaultu| 1|Izvor po defaultu|0.000000| 0.000000|US |...|i jedan „sklad» (inventory_stock):
stock_id|name |
--------|-------------|
1|Default Stock|«Source» predstavlja fizično skladišče za izdelke (vnos vsebuje fizične koordinate in naslov). «Skladišče» predstavlja logično združitev večjih «viri» (inventory_source_stock_link)
link_id|stock_id|source_code|priority|
-------|--------|-----------|--------|
1| 1|default | 1|na ravni, kjer pride do povezave s prodajnim kanalom (inventory_stock_sales_channel)
type |code|stock_id|
-------|----|--------|
website|base| 1|Glede na strukturo podatkov se domneva, da so različne vrste prodajnih kanalov, vendar se privzeto uporablja le povezava «stock«-«website» (povezava na spletno stran poteka preko kode spletne strani — base).
Ena «sklad» je lahko povezana z več «viri«, ena «vir» pa z več «skladi» (razmerje «mnogi-na-mnogi»). Izključki so privzeti «vir» and «sklad«. Ni mogoče ponovno povezovati z drugimi entitetami (omejitev na ravni kodiranja — pojavi se napaka «Can not save link related to Default Source or Default Stock«). Več o strukturi MSI v Magento 2 lahko preberete v članku ««.
Uporabljal bom privzeto konfiguracijo in dodal vse informacije o inventarju v vir default, ki je vključen v prodajni kanal, povezan s spletno stranjo s kodo base (ustreza odjemalskemu delu trgovine — glej. 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);
}Po dodanju inventarskih podatkov k izdelku v administraciji nastane takšna slika:

Mediјa
Pri «ročnem» dodajanju slik izdelkom preko administracije se ustrezne informacije zabeležijo v naslednjih tabelah:
catalog_product_entity_media_gallery: medijska knjiga (slike in video datoteke);catalog_product_entity_media_gallery_value: povezava medijskega vira z izdelki in vitrinami (lokalizacija);catalog_product_entity_media_gallery_value_to_entity: povezava medijskega vira samo z izdelki (domnevno privzeta medijska vsebina za izdelek);catalog_product_entity_varchar: tukaj se hranijo vloge, v katerih se uporablja slika;
sama slika pa se hrani v katalogu .\/pub\/media\/catalog\/product\/x\/y\/, gde x in y — prvi in drugi črki imena datoteke s sliko. Na primer, datoteka image.png mora biti shranjena kot ./pub/media/catalog/product/i/m/image.png, da lahko platforma uporablja kot sliko pri opisu izdelkov iz kataloga.
catalog_product_entity_media_gallery
Registriramo gostitelja v .\/pub\/media\/catalog\/product\/ medijski datoteki (postopek nalaganja datoteke v tem članku ni obravnavan):
function createMediaGallery($imgPathPrefixed)
{
$attrId = /* pridobi ID atribute po atributni kodi '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' ali 'video' */
'media_type' => 'image',
'disabled' => false
];
$conn->insert($table, $bind);
$result = $conn->lastInsertId($table);
return $result;
}Novo medialno datoteko pridobi identifikator ob registraciji.
catalog_product_entity_media_gallery_value
Povežemo registrirano medialno datoteko s pripadajočim izdelkom za default-vitrino:
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,
/* privzeto uporabite pogleda trgovine admin */
'store_id' => 0,
'entity_id' => $prodId,
'label' => null,
/* imamo samo eno sliko */
'position' => 1,
'disabled' => false
];
$conn->insert($table, $bind);
}catalog_product_entity_media_gallery_value_to_entity
Povežemo registrirano medialno datoteko s pripadajočim izdelkom brez vezave na katerokoli vitrino. Ni jasno, kje natančno se ti podatki uporabljajo in zakaj se ne moremo sklicevati na podatke prejšnje tabele, toda ta tabela obstaja in podatki se vanjo zapisujejo pri dodajanju slike k izdelku. Zato je tako.
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
Medijska datoteka se lahko uporablja z različnimi vlogami (koda ustreznega atributa je navedena v oklepaju):
- Osnovna (
image) - Majhna slika (
small_image) - Miniatura (
thumbnail) - Slika vzorca (
swatch_image)
Povezava vlog k medialni datoteki poteka prav v catalog_product_entity_varchar. Koda povezave je podobna kodi v razdelku „Osnovni atributi proizvoda«.
Po dodajanju slike k izdelku v administraciji izgleda tako:

Kategorije
Glavne tabele, v katerih so shranjeni podatki o kategorijah:
catalog_category_entity: register kategorij;catalog_category_product: povezava izdelkov in kategorij;catalog_category_entity_*: vrednosti EAV atributov;
Na začetku, v prazni aplikaciji Magento registriramo 2 kategoriji (skrajšal sem imena stolpcev: 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|Kategorija z id=1 je koren celotnega Magento kataloga in ni dostopna niti v administraciji niti na sprednji strani. Kategorija z id=2 (Default Category) je korenska kategorija za glavno trgovino glavne spletne strani (Glavna spletna trgovina), ki se ustvari ob namestitvi aplikacije (glej Admin / Trgovine / Vse trgovine). Koreninska kategorija trgovine na sprednji strani prav tako ni dostopna, le njene podkategorije.
Ker je tema tega članka vseeno uvoz podatkov o izdelkih, pri ustvarjanju kategorij ne bom uporabil neposrednega zapisovanja v bazo, temveč bom izkoristil razrede, ki jih zagotavlja Magento (modeli in repozitoriji). Neposredno zapisovanje v bazo se uporablja le za povezavo uvoženega izdelka s kategorijo (ujemanje kategorije se zgodi po njenem imenu, pri ujemanju se pridobi id kategorije):
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);
}Po dodajanju povezave produkta s kategorijama „Kategorija 1“ in „Kategorija 2“ se podrobnosti produkta v administraciji prikazujejo nekako takole:

Dodatne operacije
Po zaključku uvoza podatkov je treba izvesti naslednje dodatne operacije:
- indeksacija podatkov: klic v konzoli
./bin/magento indexer:reindex; - ponovna generacija URL-jev za produkte/kategorije: lahko uporabite razširitev „«
Produkti v administraciji po izvedbi dodatnih operacij:

in na sprednji strani:

Резюме
Isti sklop proizvodov (10 kosov), kot v prejšnjem članku, se uvaža vsaj za en vrstni red hitreje (1 sekunda proti 10). Za točnejšo oceno hitrosti je potrebno večje število proizvodov — nekaj sto, najbolje tisoč. Kljub temu, tudi pri tako majhnem obsegu vhodnih podatkov lahko sklepamo, da uporaba orodij, ki jih ponuja Magento (modeli in repozitoriji), znatno (izpostavljam — znatno!) pospešujejo razvoj potrebne funkcionalnosti, pri čemer pa znatno (poudarjam — znatno!) znižujejo hitrost vnosa podatkov v bazo.
Na koncu se je izkazalo, da je voda mokra, kar ni nič novega. Kljub temu pa imam zdaj kodo, s katero se lahko še naprej igram in morda naredim zanimivejše ugotovitve.
Izvor: habr.com
