Magento 2: importe productos directamente a la base de datos

В artículo anterior Describí el proceso de importación de productos a Magento 2 de la forma habitual: a través de modelos y repositorios. El método habitual tiene una velocidad de procesamiento de datos muy baja. Mi computadora portátil producía aproximadamente un producto por segundo. En esta continuación, consideraré una forma alternativa de importar un producto: ingresando directamente a la base de datos, sin pasar por los mecanismos estándar de Magento 2 (modelos, fábricas, repositorios). La secuencia de pasos para importar productos se puede adaptar a cualquier lenguaje de programación que pueda funcionar con MySQL.

Observación: Magento tiene una funcionalidad lista para usar importación de datos y lo más probable es que sea suficiente para ti. Sin embargo, si necesita un control más completo sobre el proceso de importación, sin limitarse a preparar un archivo CSV para lo que tiene, bienvenido a cat.

Magento 2: importe productos directamente a la base de datos

El código resultante de escribir ambos artículos se puede ver en el módulo Magento "flancer32/mage2_ext_demo_import". Aquí hay algunas restricciones que seguí para simplificar el código del módulo de demostración:

  • Los productos sólo se crean, no se actualizan.
  • Un almacén
  • Sólo se importan los nombres de las categorías, sin su estructura.
  • Las estructuras de datos cumplen con la versión 2.3.

JSON para importar un solo producto:

{
  "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"
}

Resumen de las principales etapas de la importación.

  • registro del producto en sí
  • conexión entre el producto y el sitio web
  • atributos básicos del producto (EAV)
  • datos de inventario (cantidad de producto en stock)
  • medios (fotos)
  • conexión con las categorías del catálogo

Registración del producto

La información básica del producto se puede encontrar en 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`)
)

La información mínima requerida para crear una entrada en el registro de producto es:

  • attribute_set_id
  • sku

adicional:

  • type_id — si no lo especificamos, entonces se usará 'simple'

Para escribir directamente en la base de datos, utilizo el adaptador DB del propio 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;
}

Después de registrar el producto con catalog_product_entity se vuelve visible en el panel de administración, en la cuadrícula de productos (Catálogo/Productos).

Magento 2: importe productos directamente a la base de datos

Relación entre producto y sitio web

La asociación del producto con el sitio determina en qué tiendas y exhibidores estará disponible el producto en el frente.

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);
}

Magento 2: importe productos directamente a la base de datos

Atributos básicos del producto

El producto recién registrado aún no tiene nombre ni descripción. Todo esto se hace a través Atributos del EAV. Aquí hay una lista de atributos básicos del producto que se necesitan para que el producto se muestre correctamente en el frente:

  • name
  • price
  • description
  • short_description
  • status
  • tax_class_id
  • url_key
  • visibility

Se agrega un atributo separado a un producto como este (se omiten los detalles sobre cómo obtener el identificador y el tipo de atributo de su código):

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);
    }
}

Usando el código de atributo, determinamos su identificación y tipo de datos (datetime, decimal, int, text, varchar), luego escriba los datos para la ventana administrativa en la tabla apropiada (store_id = 0).

Después de agregar los atributos anteriores al producto, obtendrá esta imagen en el panel de administración:

Magento 2: importe productos directamente a la base de datos

datos de inventario

A partir de la versión 2.3 de Magento, existen dos conjuntos paralelos de tablas que proporcionan almacenamiento de información de inventario (cantidad de producto):

  • cataloginventory_*: estructura antigua;
  • inventory_*: nueva estructura (MSI - Inventario de múltiples fuentes);

Necesita agregar datos de inventario a ambas estructuras, porque la nueva estructura aún no es completamente independiente de la anterior (es muy probable que por default almacén en la nueva estructura interviene una mesa cataloginventory_stock_status como inventory_stock_1).

catálogoinventario_

Al implementar Magneto 2.3, inicialmente tenemos 2 entradas en store_website, que corresponde a dos sitios: administrativo y cliente principal:

website_id|code |name        |sort_order|default_group_id|is_default|
----------|-----|------------|----------|----------------|----------|
         0|admin|Admin       |         0|               0|         0|
         1|base |Main Website|         0|               1|         1|

Mesa cataloginventory_stock solo tenemos una entrada:

stock_id|website_id|stock_name|
--------|----------|----------|
       1|         0|Default   |

Es decir, en nuestra antigua estructura solo hay un “almacén” (stock) y está vinculado al sitio web administrativo. Agregar nuevos a través del panel de administración sources/stocks en MSI (nueva estructura) no da como resultado nuevas entradas en cataloginventory_stock.

Los datos de inventario de productos en la estructura anterior se registran inicialmente en tablas:

  • cataloginventory_stock_item
  • cataloginventory_stock_status

catalogo_inventario_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);
}

catalogoinventario_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);
}

inventario_

Inicialmente, la nueva estructura para almacenar datos de inventario contiene 1 "fuente"(inventory_source):

source_code|name          |enabled|description   |latitude|longitude|country_id|...|
-----------|--------------|-------|--------------|--------|---------|----------|...|
default    |Default Source|      1|Default Source|0.000000| 0.000000|US        |...|

y uno "almacén"(inventory_stock):

stock_id|name         |
--------|-------------|
       1|Default Stock|

«fuente» representa el almacenamiento físico de los productos (el registro contiene coordenadas físicas y dirección postal). "Almacén"es una unión lógica de varias "fuentes" (inventory_source_stock_link)

link_id|stock_id|source_code|priority|
-------|--------|-----------|--------|
      1|       1|default    |       1|

en el nivel en el que se produce la conexión con el canal de ventas (inventory_stock_sales_channel)

type   |code|stock_id|
-------|----|--------|
website|base|       1|

A juzgar por la estructura de datos, se suponen varios tipos de canales de venta, pero por defecto solo la conexión "en stock"-"página web del NDN Collective "(el enlace al sitio web sigue el código del sitio web - base).

Uno "almacén"se puede vincular a varios"fuentes"y uno "fuente" - a varios "almacenes"(relación de muchos a muchos). Las excepciones son predeterminadas "fuente"Y"almacén". No se vuelven a vincular a otras entidades (limitación a nivel de código - el error "No se puede guardar el enlace relacionado con la fuente predeterminada o el stock predeterminado"). Se pueden encontrar más detalles sobre la estructura MSI en Magento 2 en el artículo “Sistema de gestión de almacenes mediante CQRS y Event Sourcing. Diseño«.

Usaré la configuración predeterminada y agregaré toda la información del inventario a la fuente. default, que interviene en el canal de ventas asociado al sitio web con el código base (corresponde al frente de la tienda - ver 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);
}

Después de agregar datos de inventario al producto en el panel de administración, obtienes esta imagen:

Magento 2: importe productos directamente a la base de datos

Medios de comunicación

Al agregar “manualmente” una imagen a un producto a través del panel de administración, la información relevante se anota en las siguientes tablas:

  • catalog_product_entity_media_gallery: registro de medios (imágenes y archivos de vídeo);
  • catalog_product_entity_media_gallery_value: vinculación de medios con productos y escaparates (localización);
  • catalog_product_entity_media_gallery_value_to_entity: vincular medios únicamente a productos (presumiblemente contenido multimedia predeterminado para el producto);
  • catalog_product_entity_varchar: Aquí se almacenan los roles en los que se utiliza la imagen;

y las imágenes mismas se guardan en el directorio ./pub/media/catalog/product/x/y/Donde x и y — la primera y segunda letras del nombre del archivo de imagen. Por ejemplo, archivo image.png debe guardarse como ./pub/media/catalog/product/i/m/image.png, para que la plataforma pueda utilizarlo como imagen a la hora de describir productos del catálogo.

Registro publicado en ./pub/media/catalog/product/ archivo multimedia (el proceso de colocación del archivo en sí no se analiza en este artículo):

function createMediaGallery($imgPathPrefixed)
{
    $attrId = /* get attribute ID by attribute code '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' or 'video' */
        'media_type' => 'image',
        'disabled' => false
    ];
    $conn->insert($table, $bind);
    $result = $conn->lastInsertId($table);
    return $result;
}

Cuando se registra, a un nuevo archivo multimedia se le asigna un identificador.

Asociamos el archivo multimedia registrado con el producto correspondiente para el escaparate predeterminado:

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,
        /* use admin store view by default */
        'store_id' => 0,
        'entity_id' => $prodId,
        'label' => null,
        /* we have one only image */
        'position' => 1,
        'disabled' => false
    ];
    $conn->insert($table, $bind);
}

Asociamos el archivo multimedia registrado con el producto correspondiente sin estar vinculados a ningún escaparate. No está claro dónde se utilizan exactamente estos datos y por qué es imposible acceder a los datos de la tabla anterior, pero esta tabla existe y los datos se escriben en ella cuando se agrega una imagen al producto. Eso es todo.

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 archivo multimedia se puede utilizar con diferentes roles (el código de atributo correspondiente se indica entre paréntesis):

  • base (image)
  • Imagen pequeña (small_image)
  • Miniatura (thumbnail)
  • Imagen de muestra (swatch_image)

Vincular roles a un archivo multimedia es exactamente lo que sucede en catalog_product_entity_varchar. El código vinculante es similar al código en "Atributos básicos del producto«.

Después de agregar una imagen al producto en el panel de administración, se ve así:

Magento 2: importe productos directamente a la base de datos

categoría

Tablas principales que contienen datos por categoría:

  • catalog_category_entity: registro de categorías;
  • catalog_category_product: conexión entre productos y categorías;
  • catalog_category_entity_*: valores de atributos EAV;

Inicialmente, en una aplicación Magento vacía, el registro de categorías contiene 2 categorías (acorté los nombres de las columnas: 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|

La categoría con id=1 es la raíz de todo el catálogo de Magento y no está disponible ni en el panel de administración ni en la página principal. Categoría con id=2 (Default Category) es la categoría raíz de la tienda principal del sitio principal (Tienda principal del sitio web) creado cuando se implementa la aplicación (consulte. Administrador / Tiendas / Todas las tiendas). Además, la categoría raíz de la tienda en sí tampoco está disponible en la parte frontal, solo sus subcategorías.

Dado que el tema de este artículo sigue siendo la importación de datos sobre productos, no utilizaré la entrada directa a la base de datos al crear categorías, sino que utilizaré las clases proporcionadas por Magento (modelos y repositorios). La entrada directa a la base de datos se utiliza únicamente para asociar el producto importado con una categoría (la categoría coincide con su nombre y la identificación de la categoría se recupera durante la comparación):

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);
}

Después de agregar un enlace de producto a las categorías "Categoría 1" y "Categoría 2", los detalles del producto en el panel de administración se ven así:

Magento 2: importe productos directamente a la base de datos

Acciones adicionales

Una vez que se completa la importación de datos, debe completar los siguientes pasos adicionales:

  • indexación de datos: llamada en la consola ./bin/magento indexer:reindex;
  • regeneración de URL para productos/categorías: puedes usar la extensión “elgentos/regenerar-catalogo-urls«

Productos en el panel de administración después de realizar acciones adicionales:

Magento 2: importe productos directamente a la base de datos

y al frente:

Magento 2: importe productos directamente a la base de datos

Resumen

El mismo conjunto de productos (10 piezas) que en el artículo anterior se importa al menos un orden de magnitud más rápido (1 segundo frente a 10). Para estimar con mayor precisión la velocidad, necesita una mayor cantidad de productos: varios cientos o, mejor aún, miles. Sin embargo, incluso con un tamaño tan pequeño de datos de entrada, podemos concluir que el uso de las herramientas proporcionadas por Magento (modelos y repositorios) es significativo (enfatizo: mucho!) acelerar el desarrollo de la funcionalidad requerida, pero al mismo tiempo significativamente (enfatizo - mucho!) reduce la velocidad a la que los datos ingresan a la base de datos.

Como resultado, el agua resultó mojada y esto no es una revelación. Sin embargo, ahora tengo el código para jugar y tal vez llegar a conclusiones más interesantes.

Fuente: habr.com