Magento 2: import produktów z zewnętrznych źródeł

Magento to rozwiązanie e-commerce, co oznacza, że jest bardziej nastawione na sprzedaż produktów niż na współdziałające z nią zadania związane z magazynowaniem, logistyką czy finansami. W tym zakresie lepiej sprawdzają się inne aplikacje (np. systemy ERP). Dlatego w praktyce korzystania z Magento często pojawia się potrzeba integracji sklepu z innymi systemami (np. z 1C).

Generalnie integrację można sprowadzić do replikacji danych w zakresie:

  • katalogu (produkty, kategorie);
  • danych o inwentarzu (stan produktów na magazynie oraz ceny);
  • klientów;
  • zamówień;

Magento oferuje osobną klasę obiektów do manipulacji danymi w bazie — repozytorium. Ze względu na specyfikę Magento dodawanie danych do bazy za pośrednictwem repozytoriów jest stosunkowo łatwe do zakodowania, ale odbywa się, powiedzmy, niezbyt szybko. W tym artykule omówię główne etapy programowego dodawania produktu do Magento 2 w "klasyczny" sposób — z wykorzystaniem klas repozytoryjnych.

Klienci i zamówienia są zazwyczaj replikowane w drugą stronę — z Magento do zewnętrznych systemów ERP. Dlatego ich obsługa jest prostsza, w Magento trzeba jedynie wybrać odpowiednie dane, a następnie — „z naszej strony wystrzeliły kule«.

Zasady zapisu danych do bazy

Obecnie tworzenie obiektów przechowywanych w bazie w sposób programowy w Magento odbywa się poprzez Factory:

function __construct (MagentoCmsModelBlockFactory $blockFactory) {
    $this->blockFactory = $blockFactory;
}

/** @var MagentoCmsModelBlock $block *//
$block = $this->blockFactory->create();

a zapis do bazy — przez Repozytorium:

function __construct (MagentoCmsApiBlockRepositoryInterface $blockRepo) {
    $this->blockRepo = $blockRepo;
}

$this->blockRepo->save($block);

Podejście z wykorzystaniem „Factory” i „Repository” można stosować do wszystkich głównych modeli w dziedzinie Magento 2.

Podstawowe informacje o produkcie

Rozpatruję strukturę danych, odpowiadającą wersji Magento 2.3. Najważniejsze informacje o produkcie znajdują się w tabeli catalog_product_entity (rejestr produktów):

entity_id
attribute_set_id
type_id
sku
has_options
required_options
created_at
updated_at

Ograniczam się do jednego typu produktu (type_id='simple'), zestawu atrybutów domyślnych (attribute_set_id=4) i ignoruję atrybuty has_options i required_options. Ponieważ atrybuty entity_id, SELECT now() as created_at, cpu.* FROM (SELECT DISTINCT tags_id from cpu) base ASOF LEFT JOIN cpu USING (tags_id, created_at) i updated_at są generowane automatycznie, w zasadzie do dodania nowego produktu wystarczy, że określimy sku. Robię to w ten sposób:

/** @var MagentoCatalogApiDataProductInterfaceFactory $factProd */
/** @var MagentoCatalogApiProductRepositoryInterface $repoProd */
/** @var MagentoCatalogApiDataProductInterface $prod */
$prod = $factProd->create();
$prod->setAttributeSetId(4);
$prod->setTypeId('simple');
$prod->setSku($sku);
$repoProd->save($prod);

i otrzymuję wyjątek:

Wartość atrybutu "Nazwa produktu" jest pusta. Ustaw atrybut i spróbuj ponownie.

Dodaję do zapytania nazwę produktu i otrzymuję wiadomość, że brakuje atrybutu Cena. Po dodaniu ceny produkt jest zapisywany w bazie:

$prod = $factProd->create();
$prod->setAttributeSetId(4);
$prod->setTypeId('simple');
$prod->setSku($sku);
$prod->setName($name);
$prod->setPrice($price);
$repoProd->save($prod);

Nazwa produktu jest zapisywana w tabeli atrybutów varchar (catalog_product_entity_varchar), cena — w tabeli catalog_product_entity_decimal. Przed dodaniem produktu warto wyraźnie wskazać, że korzystamy z administracyjnego interfejsu do importu danych:

/** @var MagentoStoreModelStoreManagerInterface $manStore */
$manStore->setCurrentStore(0);

Dodatkowe atrybuty

Obsługa dodatkowych atrybutów produktów w systemie Magento to czysta przyjemność. Model danych EAV dla podstawowych encji (patrz tabela eav_entity_type) — to jedna z kluczowych cech tej platformy. Po prostu dodajemy odpowiednie atrybuty do modelu produktu:

$prodEntity->setData('description', $desc);
$prodEntity->setData('short_description', $desc_short);
// lub
$prodEntity->setDescription($desc);
$prodEntity->setShortDescription($desc_short);

i podczas zapisywania modelu przez obiekt repozytorium:

$repoProd->save($prod);

dodatkowe atrybuty zostaną również zapisane w odpowiednich tabelach bazy danych.

Dane inwentaryzacyjne

Prosto mówiąc — ilość produktu na magazynie. W Magento 2.3 struktury w bazie danych opisujące format przechowywania danych inwentaryzacyjnych, znacznie różnią się od tych wcześniejszych. Niemniej jednak, dodanie ilości produktu na magazynie przez model produktu nie jest znacznie bardziej skomplikowane niż dodanie innych atrybutów:

/** @var MagentoCatalogModelProduct $prodEntity */
/** @var MagentoCatalogApiProductRepositoryInterface $repoProd */
$inventory = [
    'is_in_stock' => true,
    'qty' => 1234
];
$prodEntity->setData('quantity_and_stock_status', $inventory);
$repoProd->save($prodEntity);

Media

Zazwyczaj media towarzyszące produktowi dla klienta w sklepie (e-commerce) różnią się od mediów towarzyszących temu samemu produktowi dla pracownika w wewnętrznym systemie ERP. W pierwszym przypadku warto pokazać „towar z przodu”, w drugim — wystarczy ogólne przedstawienie produktu. Niemniej jednak przeniesienie przynajmniej początkowego obrazu produktu — to dość powszechna case przy imporcie danych.

Podczas dodawania obrazu przez panel administracyjny obraz najpierw jest zapisywany w katalogu tymczasowym (./pub/media/tmp/catalog/product) i dopiero podczas zapisywania produktu przenoszony jest do katalogu mediów (./pub/media/catalog/product). Również podczas dodawania przez panel administracyjny obrazowi przypisywane są tagi image, small_image, thumbnail, swatch_image.

/** @var MagentoCatalogApiProductRepositoryInterface $repoProd */
/** @var MagentoCatalogModelProductGalleryCreateHandler $hndlGalleryCreate */
/* $imagePath = '/path/to/file.png';  $imagePathRelative = '/f/i/file.png' */
$imagePathRelative = $this->imagePlaceToTmpMedia($imagePath);
/* reload product with gallery data */
$product = $repoProd->get($sku);
/* add image to product's gallery */
$gallery['images'][] = [
    'file' => $imagePathRelative,
    'media_type' => 'image'
    'label' => ''
];
$product->setData('media_gallery', $gallery);
/* set usage areas */
$product->setData('image', $imagePathRelative);
$product->setData('small_image', $imagePathRelative);
$product->setData('thumbnail', $imagePathRelative);
$product->setData('swatch_image', $imagePathRelative);
/* create product's gallery */
$hndlGalleryCreate->execute($product);

Dlaczegoś media są podpinane dopiero po wcześniejszym zapisaniu produktu i ponownym pobraniu go z repozytorium. Należy również podać atrybut label przy dodawaniu wpisu do galerii mediów produktu (w przeciwnym razie otrzymujemy wyjątek Nieokreślony indeks: label w .../module-catalog/Model/Product/Gallery/CreateHandler.php w linii 516).

Kategorie

Struktura kategorii sklepu oraz aplikacji backendowej, a także umiejscowienie w nich produktów może znacząco się różnić. Strategie przenoszenia danych o kategoriach oraz produktach w nich zależą od wielu czynników. W tym przykładzie trzymam się następującej:

  • kategorie backendu i sklepu są dopasowywane na podstawie nazwy;
  • jeśli importowana jest kategoria, której nie ma w sklepie, to zostaje utworzona pod kategorią główną (Domyślna kategoria) a jej dalsze pozycjonowanie w katalogu sklepu odbywa się ręcznie;
  • przypisanie produktu do kategorii odbywa się tylko podczas jego tworzenia w sklepie (pierwszy import);

Podstawowe informacje o kategorii znajdują się w tabeli catalog_category_entity (katalog kategorii). Tworzenie kategorii w Magento:

/** @var MagentoCatalogApiDataCategoryInterfaceFactory $factCat */
/** @var MagentoCatalogApiCategoryRepositoryInterface $repoCat */
$cat = $factCat->create();
$cat->setName($name);
$cat->setIsActive(true);
$repoCat->save($cat);

Przypisanie produktu do kategorii odbywa się na podstawie ID kategorii i SKU produktu:

/** @var MagentoCatalogModelCategoryProductLinkFactory $factCatProdLink */
/** @var MagentoCatalogApiCategoryLinkRepositoryInterface $repoCatLink */
$link = $factCatProdLink->create();
$link->setCategoryId($catMageId);
$link->setSku($prodSku);
$repoCatLink->save($link);

Podsumowując

Napisanie kodu do programowego dodawania produktów w Magento 2 jest dość proste. Wszystko, co zostało powiedziane, zebrałem w demo-moduł „flancer32/mage2_ext_demo_import„. W module jest tylko jedna komenda konsolowa fl32:import:prod, która importuje produkty opisane w pliku JSON „./etc/data/products.json«:

[
  {
    "sku": "...",
    "name": "...",
    "desc": "...",
    "desc_short": "...",
    "price": ..., 
    "qty": ..., 
    "categories": ["..."],
    "image_path": "..."
  }
]

Obrazy do importu znajdują się w katalogu ./etc/data/img.

Czas importu 10 produktów w ten sposób wynosi około 10 sekund na moim laptopie. Jeśli drążyć tę myśl dalej, można łatwo dojść do wniosku, że w ciągu godziny można zaimportować około 3600 produktów, a import 100K produktów może zająć około 30 godzin. Zmiana laptopa na serwer może nieco poprawić sytuację. Może nawet kilkukrotnie. Ale nie w innej skali. Może ta prędkość jest w pewnym stopniu jedną z przyczyn powstania projektu magento/async-import.

Radikalnym rozwiązaniem, które przyspieszy import, może być bezpośrednie zapisanie do bazy danych, ale w takim przypadku traci się wszystkie „plusy” związane z rozszerzalnością Magento — wszystko „rozszerzone” trzeba będzie zrobić samodzielnie. Niemniej jednak, warto. Jeśli się uda, rozważę podejście z bezpośrednim zapisem do Bazy Danych w następnym artykule.

Źródło: habr.com

Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS 🔥 Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS | ProHoster