Magento 2: importuj produkty bezpośrednio do bazy danych
В Poprzedni artykuł Opisałem proces importu produktów do Magento 2 w standardowy sposób – poprzez modele i repozytoria. Zwykła metoda ma bardzo niską prędkość przetwarzania danych. Mój laptop wytwarzał około jednego produktu na sekundę. W tej kontynuacji rozważam alternatywny sposób importu produktu - poprzez bezpośredni wpis do bazy danych, z pominięciem standardowych mechanizmów Magento 2 (modele, fabryki, repozytoria). Kolejność kroków importu produktów można dostosować do dowolnego języka programowania współpracującego z MySQL.
Odpowiedzialność: Magento posiada gotową funkcjonalność dla import danych i najprawdopodobniej to ci wystarczy. Jeśli jednak potrzebujesz pełniejszej kontroli nad procesem importu, nie ograniczającej się do przygotowania pliku CSV dla tego, co posiadasz, zapraszamy do cat.
Kod powstały w wyniku napisania obu artykułów można obejrzeć w module Magento”flacer32/mage2_ext_demo_import„. Oto kilka ograniczeń, których przestrzegałem, aby uprościć kod modułu demonstracyjnego:
Produkty są jedynie tworzone, a nie aktualizowane.
Jeden magazyn
Importowane są tylko nazwy kategorii, bez ich struktury
Struktury danych są zgodne z wersją 2.3
JSON do importowania pojedynczego produktu:
{
"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"
}
Przegląd głównych etapów importu
rejestracja samego produktu
połączenie pomiędzy produktem a stroną internetową
podstawowe cechy produktu (EAV)
dane magazynowe (ilość produktu w magazynie)
multimedia (zdjęcia)
powiązanie z kategoriami katalogowymi
Rejestracja produktu
Podstawowe informacje o produkcie można znaleźć w 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`)
)
Minimalne informacje wymagane do utworzenia wpisu do rejestru wyrobów to:
attribute_set_id
sku
dodatkowy:
type_id — jeśli tego nie określimy, zostanie użyte słowo „prosty”.
Aby pisać bezpośrednio do bazy danych, używam adaptera DB samego Magento:
Nowo zarejestrowany produkt nie ma jeszcze nazwy ani opisu. Wszystko to odbywa się poprzez Atrybuty EAV. Oto lista podstawowych atrybutów produktu, które są potrzebne, aby produkt poprawnie prezentował się na froncie:
name
price
description
short_description
status
tax_class_id
url_key
visibility
Do takiego produktu dodawany jest osobny atrybut (pomijane są szczegóły dotyczące uzyskania identyfikatora i typu atrybutu z jego 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);
}
}
Za pomocą kodu atrybutu określamy jego identyfikator i typ danych (datetime, decimal, int, text, varchar), następnie wpisz dane dla okna administracyjnego do odpowiedniej tabeli (store_id = 0).
Po dodaniu powyższych atrybutów do produktu w panelu administracyjnym pojawia się taki obrazek:
Dane inwentarza
Począwszy od wersji 2.3 w Magento istnieją dwa równoległe zestawy tabel, które umożliwiają przechowywanie informacji o stanie magazynowym (ilości produktu):
cataloginventory_*: stara konstrukcja;
inventory_*: nowa struktura (MSI – Multi Source Inventory);
Trzeba dodać dane inwentaryzacyjne do obu struktur, bo nowa struktura nie jest jeszcze całkowicie niezależna od starej (jest bardzo prawdopodobne, że dla default magazyn w nowej strukturze zaangażowany jest stół cataloginventory_stock_status jak inventory_stock_1).
kataloginwentarz_
Podczas wdrażania Magneto 2.3 początkowo mamy 2 wpisy store_website, co odpowiada dwóm stronom - administracyjnemu i głównemu klientowi:
Oznacza to, że w naszej starej strukturze jest tylko jeden „magazyn” (stock) i jest powiązany ze stroną administracyjną. Dodawanie nowych poprzez panel administracyjny sources/stocks w MSI (nowa struktura) nie powoduje nowych wpisów w cataloginventory_stock.
Dane inwentaryzacyjne produktów w starej strukturze ujęte są początkowo w tabelach:
cataloginventory_stock_item
cataloginventory_stock_status
kataloginwentarz_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);
}
spis_
Początkowo nowa struktura przechowywania danych inwentaryzacyjnych zawiera 1 "źródło"(inventory_source):
«źródło» reprezentuje fizyczne miejsce przechowywania produktów (rekord zawiera współrzędne fizyczne i adres pocztowy). "Magazyn„jest logicznym połączeniem kilku„ źródeł ”(inventory_source_stock_link)
na poziomie, na którym następuje połączenie z kanałem sprzedaży (inventory_stock_sales_channel)
type |code|stock_id|
-------|----|--------|
website|base| 1|
Sądząc po strukturze danych, zakłada się różne rodzaje kanałów sprzedaży, ale domyślnie tylko połączenie „stany magazynowe„-”"(link do strony internetowej następuje po kodzie strony - base).
Jeden "magazyn„można powiązać z kilkoma”źródła"i jeden "źródło" - do kilku "magazyny„(relacja wiele do wielu). Wyjątki są domyślne”źródło"A"magazyn„. Nie są one ponownie powiązane z innymi podmiotami (ograniczenie na poziomie kodu – błąd „Nie można zapisać łącza powiązanego z domyślnym źródłem lub domyślnym zapasem„). Więcej szczegółów na temat struktury MSI w Magento 2 znajdziesz w artykule „System zarządzania magazynem wykorzystujący CQRS i Event Sourcing. Projekt".
Użyję konfiguracji domyślnej i dodam wszystkie informacje o zasobach do źródła default, który bierze udział w kanale sprzedaży powiązanym z witryną z kodem base (odpowiada frontowi sklepu – patrz store_website):
Po dodaniu danych magazynowych do produktu w panelu administracyjnym otrzymamy taki obraz:
Media
W przypadku „ręcznego” dodawania zdjęcia do produktu poprzez panel administracyjny, odpowiednie informacje zapisywane są w poniższych tabelach:
catalog_product_entity_media_gallery: rejestr multimediów (zdjęcia i pliki wideo);
catalog_product_entity_media_gallery_value: łączenie mediów z produktami i prezentacjami (lokalizacja);
catalog_product_entity_media_gallery_value_to_entity: łączenie multimediów tylko z produktami (prawdopodobnie domyślna zawartość multimedialna dla produktu);
catalog_product_entity_varchar: Tutaj przechowywane są role, w jakich obraz jest używany;
a same obrazy są zapisywane w katalogu ./pub/media/catalog/product/x/y/Gdzie x и y — pierwsza i druga litera nazwy pliku obrazu. Na przykład plik image.png należy zapisać jako ./pub/media/catalog/product/i/m/image.png, aby platforma mogła wykorzystać go jako obraz przy opisywaniu produktów z katalogu.
katalog_produkt_entity_media_galeria
Rejestracja opublikowana w ./pub/media/catalog/product/ plik multimedialny (sam proces umieszczania pliku nie jest omawiany w tym artykule):
Łączymy zarejestrowany plik multimedialny z odpowiednim produktem, nie będąc przywiązanym do żadnej witryny sklepowej. Nie jest jasne, gdzie dokładnie te dane są wykorzystywane i dlaczego nie można uzyskać dostępu do danych z poprzedniej tabeli, ale tabela ta istnieje i dane są do niej zapisywane po dodaniu zdjęcia do produktu. Więc to jest to.
Pliku multimedialnego można używać w różnych rolach (w nawiasach podano odpowiedni kod atrybutu):
Baza (image)
Mały obraz (small_image)
Miniaturka (thumbnail)
Próbka obrazu (swatch_image)
Łączenie ról z plikiem multimedialnym jest dokładnie tym, co dzieje się w catalog_product_entity_varchar. Kod wiążący jest podobny do kodu w „Podstawowe cechy produktu".
Po dodaniu zdjęcia do produktu w panelu administracyjnym wygląda to tak:
Kategorie
Główne tabele zawierające dane według kategorii:
catalog_category_entity: rejestr kategorii;
catalog_category_product: powiązanie pomiędzy produktami i kategoriami;
catalog_category_entity_*: wartości atrybutów EAV;
Początkowo w pustej aplikacji Magento rejestr kategorii zawiera 2 kategorie (skróciłem nazwy kolumn: crt - created_at, upd - updated_at):
Kategoria o id=1 jest podstawą całego katalogu Magento i nie jest dostępna ani w panelu administracyjnym, ani na stronie głównej. Kategoria o identyfikatorze=2 (Domyślny Kategoria) to kategoria główna głównego sklepu witryny głównej (Główny sklep internetowy) utworzone w momencie wdrożenia aplikacji (patrz. Administrator / Sklepy / Wszystkie sklepy). Co więcej, z przodu również nie jest dostępna kategoria główna samego sklepu, a jedynie jego podkategorie.
Ponieważ tematem tego artykułu jest nadal import danych o produktach, przy tworzeniu kategorii nie będę korzystał z bezpośredniego wpisu do bazy, lecz skorzystam z klas dostarczonych przez samo Magento (modele i repozytoria). Bezpośredni wpis do bazy służy jedynie do powiązania zaimportowanego produktu z kategorią (kategoria dopasowywana jest po nazwie, a podczas dopasowywania pobierany jest identyfikator kategorii):
Produkty w panelu administracyjnym po wykonaniu dodatkowych czynności:
i z przodu:
Streszczenie
Ten sam zestaw produktów (10 sztuk), co w poprzednim artykule, jest importowany co najmniej o rząd wielkości szybciej (1 sekunda w porównaniu z 10). Aby dokładniej oszacować prędkość, potrzebna jest większa liczba produktów – kilkaset, a jeszcze lepiej tysiące. Jednak nawet przy tak małym rozmiarze danych wejściowych możemy stwierdzić, że wykorzystanie narzędzi udostępnianych przez Magento (modele i repozytoria) jest znaczące (podkreślam – dużo!) przyspieszyć rozwój wymaganej funkcjonalności, ale jednocześnie znacznie (podkreślam - dużo!) zmniejsz prędkość, z jaką dane trafiają do bazy danych.
W efekcie woda okazała się mokra i to nie jest żadna rewelacja. Jednak teraz mam kod do pobawienia się i być może dojdę do ciekawszych wniosków.