Dans J'ai décrit le processus d'importation de produits dans Magento 2 de manière classique — via des modèles et des dépôts. La méthode classique présente une vitesse de traitement des données assez faible. Sur mon ordinateur portable, cela donnait environ un produit par seconde. Dans cette suite, j'examine une méthode alternative d'importation de produits — en écrivant directement dans la base, en contournant les mécanismes standard de Magento 2 (modèles, usines, dépôts). La séquence des étapes assurant l'importation de produits peut être adaptée à n'importe quel langage de programmation capable de travailler avec MySQL.
Avertissement: Magento dispose d'une fonctionnalité prête à l'emploi pour et, très probablement, cela vous suffira. Cependant, si vous avez besoin d'un contrôle plus complet sur le processus d'importation, qui ne se limite pas à la préparation d'un fichier CSV pour ce qui existe — bienvenue sous le cat.

Le code résultant de l'écriture des deux articles peut être consulté dans le module Magento ««. Voici certaines des limitations que j'ai respectées pour simplifier le code du module de démonstration :
- Les produits sont uniquement créés, pas mis à jour.
- Un entrepôt
- Seules les catégories de noms sont importées, sans leur structure
- Les structures de données correspondent à la version 2.3
JSON pour l'importation d'un produit individuel :
{
"sku": "MVA20D-UBV-3",
"name": "Bouchon pour scellage VA47-29 IEK",
"desc": "Accès aux dispositifs ...",
"desc_short": "Le bouchon pour scellage VA47-29 IEK est destiné à ...",
"price": 5.00,
"qty": 25,
"categories": ["Catégorie 1", "Catégorie 2"],
"image_path": "mva20d_ubv_3.png"
}Aperçu des étapes clés de l'importation
- enregistrement du produit lui-même
- lien entre le produit et le site web
- attributs de base du produit (EAV)
- données d'inventaire (quantité de produit en stock)
- média (images)
- lien avec les catégories de catalogue
Enregistrement du produit
Les informations de base sur le produit se trouvent dans catalog_product_entity:
CREATE TABLE `catalog_product_entity` (
`entity_id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'Identifiant de l'entité',
`attribute_set_id` smallint(5) unsigned NOT NULL DEFAULT '0' COMMENT 'Identifiant de l'ensemble d'attributs',
`type_id` varchar(32) NOT NULL DEFAULT 'simple' COMMENT 'Identifiant de type',
`sku` varchar(64) DEFAULT NULL COMMENT 'SKU',
`has_options` smallint(6) NOT NULL DEFAULT '0' COMMENT 'A des options',
`required_options` smallint(5) unsigned NOT NULL DEFAULT '0' COMMENT 'Options requises',
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Date de création',
`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'Date de mise à jour',
PRIMARY KEY (`entity_id`),
KEY `CATALOG_PRODUCT_ENTITY_ATTRIBUTE_SET_ID` (`attribute_set_id`),
KEY `CATALOG_PRODUCT_ENTITY_SKU` (`sku`)
)Informations minimales requises pour créer un enregistrement dans le registre des produits :
id_de_l'ensemble_d'attributssku
supplémentaire :
id_type— si non précisé, ‘simple’ sera utilisé
Pour l'écriture directe dans la base, j'utilise l'adaptateur DB de 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;
}Après l'enregistrement du produit dans catalog_product_entity il devient visible dans l'interface admin, dans la grille des produits (Catalogue / Produits).

Lien entre le produit et le site web
Le lien entre le produit et le site détermine dans quels magasins et sur quelles vitrines le produit sera disponible en front.
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);
}
Attributs de base du produit
Un produit nouvellement enregistré n’a pas encore de nom ou de description. Tout cela se fait via . Voici la liste des attributs de base du produit nécessaires pour que le produit soit affiché correctement en front :
nompricedescriptionshort_descriptionstatuttax_class_idurl_keyvisibility
Un attribut particulier au produit est ajouté de la manière suivante (détails sur l'obtention de l'identifiant et du type d'attribut par son code omis) :
public function create($prodId, $attrCode, $attrValue)
{
$attrId = \/ * obtenir l'ID de l'attribut par le code d'attribut *\/
$attrType = \/ * obtenir le type d'attribut [datetime|decimal|int|text|varchar] par le code d'attribut *\/
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,
\/ * mettre tous les attributs par défaut dans la vue magasin avec id=0 (admin) *\/
'store_id' => 0,
'value' => $attrValue
];
$conn->insert($table, $bind);
}
}Par le code de l'attribut, nous déterminons son id et son type de données (datetime, decimal, int, text, varchar), puis nous écrivons les données dans la table correspondante pour la vitrine d'administration (store_id = 0).
Après avoir ajouté les attributs mentionnés ci-dessus au produit, cela donne une image comme suit dans l'interface admin :

Données d'inventaire
Depuis la version 2.3 de Magento, deux ensembles de tables existent parallèlement, permettant le stockage des informations d'inventaire (quantité du produit) :
cataloginventory_*: ancienne structure ;inventory_*: nouvelle structure (MSI — Multi Source Inventory);
Il est nécessaire d'ajouter les données d'inventaire dans les deux structures, car la nouvelle structure n'est pas encore complètement indépendante de l'ancienne (il semble que pour default l'entrepôt dans la nouvelle structure, la table cataloginventory_stock_status comme inventory_stock_1).
cataloginventory_
Lors du déploiement de Magneto 2.3, nous avons initialement 2 enregistrements dans store_website, correspondant à deux sites — un administratif et le principal client :
website_id|code |name |sort_order|default_group_id|is_default|
----------|-----|------------|----------|----------------|----------|
0|admin|Admin | 0| 0| 0|
1|base |Site Principal| 0| 1| 1|Dans la table cataloginventory_stock nous n'avons qu'un seul enregistrement :
stock_id|website_id|stock_name|
--------|----------|----------|
1| 0|Par défaut |C'est-à-dire que dans l'ancienne structure, nous avons seulement un «entrepôt» (stock) et il est lié au site administratif. L'ajout via l'interface d'administration de nouveaux sources/stocks dans MSI (la nouvelle structure) ne crée pas de nouveaux enregistrements dans cataloginventory_stock.
Les données d'inventaire sur les produits dans l'ancienne structure sont initialement enregistrées dans les tables :
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,
\/* nous n'utilisons qu'un seul stock dans la structure 'cataloginventory' par défaut *\/
'stock_id' => 1,
'qty' => $qty,
'is_qty_decimal' => $isQtyDecimal,
'is_in_stock' => $isInStock,
\/* le stock par défaut est lié au site administratif (voir `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,
\/* nous n'utilisons qu'un seul stock dans la structure 'cataloginventory' par défaut *\/
'stock_id' => 1,
'qty' => $qty,
'stock_status' => MagentoCatalogInventoryApiDataStockStatusInterface::STATUS_IN_STOCK,
\/* le stock par défaut est lié au site administratif (voir `cataloginventory_stock`) *\/
'website_id' => 0
];
$conn->insert($table, $bind);
}inventory_
Initialement, la nouvelle structure pour le stockage des données d'inventaire contient 1 «source» (inventory_source):
source_code|name |enabled|description |latitude|longitude|country_id|...|
-----------|--------------|-------|--------------|--------|---------|----------|...|
default |Source par défaut| 1|Source par défaut|0.000000| 0.000000|FR |...|et un «entrepôt» (inventory_stock):
stock_id|name |
--------|-------------|
1|Stock par défaut|«Source» représente un stockage physique pour les produits (l'enregistrement contient des coordonnées physiques et une adresse postale). «Entrepôt» représente une association logique de plusieurs «sources» (inventory_source_stock_link)
link_id|stock_id|source_code|priority|
-------|--------|-----------|--------|
1| 1|default | 1|au niveau duquel l'association à un canal de vente a lieu (inventory_stock_sales_channel)
type |code|stock_id|
-------|----|--------|
website|base| 1|Selon la structure des données, différents types de canaux de vente sont supposés, mais par défaut seule l'association «stock«-«site web» (le lien vers le site web se fait via le code du site web — est la dernière mise à jour que nous avons. Cela peut se produire en raison d'un délai réseau ou d'une mauvaise connexion Internet. Dans ce cas, nous utilisons également la dernière mise à jour que nous avons.).
Un «entrepôt» peut être lié à plusieurs «sources«, et un «source» — à plusieurs «entrepôts» (relation « plusieurs à plusieurs »). Les exceptions sont les «source» et «entrepôt« par défaut. Elles ne peuvent pas être réliées à d'autres entités (restriction au niveau du code — erreur «Can not save link related to Default Source or Default Stock«). On peut lire plus en détail sur la structure MSI dans Magento 2 dans l'article ««.
Je vais utiliser la configuration par défaut et ajouter toutes les informations d'inventaire à la source default, qui est impliquée dans le canal de vente lié au site web avec le code est la dernière mise à jour que nous avons. Cela peut se produire en raison d'un délai réseau ou d'une mauvaise connexion Internet. Dans ce cas, nous utilisons également la dernière mise à jour que nous avons. qui correspond à la partie cliente de la boutique — voir. store_website):
function createNewItem($sku, $qty)
{
\/** @var MagentoFrameworkAppResourceConnection $this->resource *\/\n \/** @var MagentoFrameworkDBAdapterPdoMysql $conn *\/\n $conn = $this->resource->getConnection();\n $table = $this->resource->getTableName('inventory_source_item');\n $bind = [\n 'source_code' => 'default',\n 'sku' => $sku,\n 'quantity' => $qty,\n 'status' => MagentoInventoryApiApiDataSourceItemInterface::STATUS_IN_STOCK\n ];\n $conn->insert($table, $bind);\n}Après avoir ajouté les données d'inventaire au produit dans l'admin, cela donne l'image suivante :

Média
Lors de l'ajout « manuel » d'une image au produit via l'admin, les informations pertinentes sont enregistrées dans les tableaux suivants :
catalog_product_entity_media_gallery: registre multimédia (images et fichiers vidéo);catalog_product_entity_media_gallery_value: association des médias aux produits et vitrines (localisation);catalog_product_entity_media_gallery_value_to_entity: association des médias uniquement aux produits (présumément, contenu multimédia par défaut pour le produit);catalog_product_entity_varchar: ici sont enregistrés les rôles dans lesquels l'image est utilisée;
et les images elles-mêmes sont enregistrées dans le répertoire ./pub/media/catalog/product/x/y/, où x et y — les deux premières lettres du nom du fichier de l'image. Par exemple, le fichier image.png doit être enregistré sous ./pub/media/catalog/product/i/m/image.png, afin que la plateforme puisse l'utiliser comme image lors de la description des produits du catalogue.
catalog_product_entity_media_gallery
Nous enregistrons le fichier hébergé dans . /pub/media/catalog/product/ fichier multimédia (le processus d'hébergement du fichier dans cet article n'est pas abordé) :
function createMediaGallery($imgPathPrefixed)
{
$attrId = /* obtenir l'ID de l'attribut par le code d'attribut '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' ou 'video' */
'media_type' => 'image',
'disabled' => false
];
$conn->insert($table, $bind);
$result = $conn->lastInsertId($table);
return $result;
}Lors de l'enregistrement, un nouvel identifiant est attribué au fichier multimédia.
catalog_product_entity_media_gallery_value
Liaison du fichier multimédia enregistré avec le produit correspondant pour la vitrine par défaut :
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,
/* utiliser la vue de magasin admin par défaut */
'store_id' => 0,
'entity_id' => $prodId,
'label' => null,
/* nous n'avons qu'une seule image */
'position' => 1,
'disabled' => false
];
$conn->insert($table, $bind);
}catalog_product_entity_media_gallery_value_to_entity
Nous lions le fichier multimédia enregistré au produit sans lien avec une quelconque vitrine. Il n'est pas clair où exactement ces données sont utilisées et pourquoi il n'est pas possible de se référer aux données de la table précédente, mais cette table existe et les données y sont enregistrées lors de l'ajout d'une image au produit. C'est pourquoi c'est ainsi.
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
Un fichier multimédia peut être utilisé avec différents rôles (le code du champ correspondant est indiqué entre parenthèses) :
- Base (
image) - Petite Image (
small_image) - Vignette (
thumbnail) - Image d'échantillon (
swatch_image)
L'attribution des rôles au fichier multimédia se fait précisément dans catalog_product_entity_varchar. Le code d'attribution est similaire à celui de la section «Attributs de base du produit«.
Après avoir ajouté une image au produit dans l'admin, cela donne :

Catégories
Les principales tables contenant des données sur les catégories :
catalog_category_entity: registre des catégories ;catalog_category_product: liaison entre les produits et les catégories ;catalog_category_entity_*: valeurs des attributs EAV ;
Initialement, dans une application Magento vide, le registre des catégories contient 2 catégories (j'ai abrégé les noms des colonnes : crt — created_at, maj — 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|La catégorie avec id=1 est la racine de tout le catalogue Magento et n'est accessible ni dans l'administration ni sur le front. La catégorie avec id=2 (Catégorie par défaut) est la catégorie racine pour le magasin principal du site principal (Main Website Store), créé lors du déploiement de l'application (voir Admin / Stores / All Stores). De plus, la catégorie racine du magasin sur le front n'est pas accessible non plus, seules ses sous-catégories le sont.
Puisque le thème de cet article est l'importation de données produit, je ne vais pas utiliser d'insertion directe dans la base de données lors de la création des catégories, mais je vais utiliser les classes fournies par Magento (modèles et dépôts). L'insertion directe dans la base est utilisée uniquement pour lier le produit importé à la catégorie (la correspondance de la catégorie se fait par son nom, lors de la correspondance l'id de la catégorie est extrait) :
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);
}Après avoir ajouté la liaison du produit avec les catégories « Catégorie 1 » et « Catégorie 2 », les détails du produit dans l'administration ressemblent à ceci :

Actions supplémentaires
Après avoir terminé l'importation des données, il est nécessaire d'effectuer les actions supplémentaires suivantes :
- indexation des données : appel depuis la console
./bin/magento indexer:reindex; - régénération des URL pour les produits/catégories : vous pouvez utiliser l'extension ««
Les produits dans l'administration après avoir effectué les actions supplémentaires :

et sur le front :

Résumé
Le même ensemble de produits (10 pièces), que dans l'article précédent, est importé, au moins, un ordre de grandeur plus rapidement (1 seconde contre 10). Pour une évaluation plus précise de la vitesse, un plus grand nombre de produits est nécessaire - plusieurs centaines, et mieux encore des milliers. Néanmoins, même avec une si petite taille de données d'entrée, on peut conclure que l'utilisation des outils fournis par Magento (modèles et dépôts) améliore considérablement (je souligne - significativement!) accélèrent le développement des fonctionnalités requises, mais réduisent également (je le souligne — significativement!) la vitesse d'entrée des données dans la base.
Au final, l'eau s'est révélée humide et ce n'est pas une révélation. Néanmoins, maintenant j'ai du code pour m'amuser davantage et peut-être faire des conclusions plus intéressantes.
Source : habr.com
