Magento2: agrega opciones de atributos de producto mediante programación

32

¿Cuál es la forma correcta (oficial) de agregar mediante programación la opción de atributo del producto en M2? Por ejemplo, para manufacturerel atributo del producto. Obviamente, la opción existente coincidiría con el valor del título "Administrador".

werd
fuente

Respuestas:

55

Este es el enfoque que se me ocurrió para manejar las opciones de atributos. Clase auxiliar:

<?php
namespace My\Module\Helper;

class Data extends \Magento\Framework\App\Helper\AbstractHelper
{
    /**
     * @var \Magento\Catalog\Api\ProductAttributeRepositoryInterface
     */
    protected $attributeRepository;

    /**
     * @var array
     */
    protected $attributeValues;

    /**
     * @var \Magento\Eav\Model\Entity\Attribute\Source\TableFactory
     */
    protected $tableFactory;

    /**
     * @var \Magento\Eav\Api\AttributeOptionManagementInterface
     */
    protected $attributeOptionManagement;

    /**
     * @var \Magento\Eav\Api\Data\AttributeOptionLabelInterfaceFactory
     */
    protected $optionLabelFactory;

    /**
     * @var \Magento\Eav\Api\Data\AttributeOptionInterfaceFactory
     */
    protected $optionFactory;

    /**
     * Data constructor.
     *
     * @param \Magento\Framework\App\Helper\Context $context
     * @param \Magento\Catalog\Api\ProductAttributeRepositoryInterface $attributeRepository
     * @param \Magento\Eav\Model\Entity\Attribute\Source\TableFactory $tableFactory
     * @param \Magento\Eav\Api\AttributeOptionManagementInterface $attributeOptionManagement
     * @param \Magento\Eav\Api\Data\AttributeOptionLabelInterfaceFactory $optionLabelFactory
     * @param \Magento\Eav\Api\Data\AttributeOptionInterfaceFactory $optionFactory
     */
    public function __construct(
        \Magento\Framework\App\Helper\Context $context,
        \Magento\Catalog\Api\ProductAttributeRepositoryInterface $attributeRepository,
        \Magento\Eav\Model\Entity\Attribute\Source\TableFactory $tableFactory,
        \Magento\Eav\Api\AttributeOptionManagementInterface $attributeOptionManagement,
        \Magento\Eav\Api\Data\AttributeOptionLabelInterfaceFactory $optionLabelFactory,
        \Magento\Eav\Api\Data\AttributeOptionInterfaceFactory $optionFactory
    ) {
        parent::__construct($context);

        $this->attributeRepository = $attributeRepository;
        $this->tableFactory = $tableFactory;
        $this->attributeOptionManagement = $attributeOptionManagement;
        $this->optionLabelFactory = $optionLabelFactory;
        $this->optionFactory = $optionFactory;
    }

    /**
     * Get attribute by code.
     *
     * @param string $attributeCode
     * @return \Magento\Catalog\Api\Data\ProductAttributeInterface
     */
    public function getAttribute($attributeCode)
    {
        return $this->attributeRepository->get($attributeCode);
    }

    /**
     * Find or create a matching attribute option
     *
     * @param string $attributeCode Attribute the option should exist in
     * @param string $label Label to find or add
     * @return int
     * @throws \Magento\Framework\Exception\LocalizedException
     */
    public function createOrGetId($attributeCode, $label)
    {
        if (strlen($label) < 1) {
            throw new \Magento\Framework\Exception\LocalizedException(
                __('Label for %1 must not be empty.', $attributeCode)
            );
        }

        // Does it already exist?
        $optionId = $this->getOptionId($attributeCode, $label);

        if (!$optionId) {
            // If no, add it.

            /** @var \Magento\Eav\Model\Entity\Attribute\OptionLabel $optionLabel */
            $optionLabel = $this->optionLabelFactory->create();
            $optionLabel->setStoreId(0);
            $optionLabel->setLabel($label);

            $option = $this->optionFactory->create();
            $option->setLabel($optionLabel);
            $option->setStoreLabels([$optionLabel]);
            $option->setSortOrder(0);
            $option->setIsDefault(false);

            $this->attributeOptionManagement->add(
                \Magento\Catalog\Model\Product::ENTITY,
                $this->getAttribute($attributeCode)->getAttributeId(),
                $option
            );

            // Get the inserted ID. Should be returned from the installer, but it isn't.
            $optionId = $this->getOptionId($attributeCode, $label, true);
        }

        return $optionId;
    }

    /**
     * Find the ID of an option matching $label, if any.
     *
     * @param string $attributeCode Attribute code
     * @param string $label Label to find
     * @param bool $force If true, will fetch the options even if they're already cached.
     * @return int|false
     */
    public function getOptionId($attributeCode, $label, $force = false)
    {
        /** @var \Magento\Catalog\Model\ResourceModel\Eav\Attribute $attribute */
        $attribute = $this->getAttribute($attributeCode);

        // Build option array if necessary
        if ($force === true || !isset($this->attributeValues[ $attribute->getAttributeId() ])) {
            $this->attributeValues[ $attribute->getAttributeId() ] = [];

            // We have to generate a new sourceModel instance each time through to prevent it from
            // referencing its _options cache. No other way to get it to pick up newly-added values.

            /** @var \Magento\Eav\Model\Entity\Attribute\Source\Table $sourceModel */
            $sourceModel = $this->tableFactory->create();
            $sourceModel->setAttribute($attribute);

            foreach ($sourceModel->getAllOptions() as $option) {
                $this->attributeValues[ $attribute->getAttributeId() ][ $option['label'] ] = $option['value'];
            }
        }

        // Return option ID if exists
        if (isset($this->attributeValues[ $attribute->getAttributeId() ][ $label ])) {
            return $this->attributeValues[ $attribute->getAttributeId() ][ $label ];
        }

        // Return false if does not exist
        return false;
    }
}

Luego, ya sea en la misma clase o incluyéndolo a través de inyección de dependencia, puede agregar u obtener su ID de opción llamando createOrGetId($attributeCode, $label).

Por ejemplo, si inyecta My\Module\Helper\Datacomo $this->moduleHelper, puede llamar a:

$manufacturerId = $this->moduleHelper->createOrGetId('manufacturer', 'ABC Corp');

Si 'ABC Corp' es un fabricante existente, extraerá la identificación. Si no, lo agregará.

ACTUALIZADO 09/09/2016: según Ruud N., la solución original utilizaba CatalogSetup, lo que provocó un error que comenzó en Magento 2.1. Esta solución revisada evita ese modelo, creando la opción y la etiqueta explícitamente. Debería funcionar en 2.0+.

Ryan Hoerr
fuente
3
Es tan oficial como vas a conseguir. Todas las búsquedas y opciones agregadas pasan por el núcleo de Magento. Mi clase es solo un contenedor para esos métodos básicos que los hace más fáciles de usar.
Ryan Hoerr
1
Hola Ryan, no deberías establecer el valor en la opción, esta es la identificación interna que usa magento y descubrí de la manera difícil que si configuras el valor en un valor de cadena con un número inicial como '123 abc corp' causa algunos problemas serios debido a la implementación de Magento\Eav\Model\ResourceModel\Entity\Attribute::_processAttributeOptions. Compruébelo usted mismo, si elimina la $option->setValue($label);declaración de su código, guardará la opción, luego, cuando la obtenga, Magento devolverá el valor de un incremento automático en la eav_attribute_optiontabla.
quickshiftin
2
si agrego esto en una función foreach, en la segunda iteración obtendré el error "Magento \ Eav \ Model \ Entity \ Attribute \ OptionManagement :: setOptionValue () debe ser del tipo cadena, objeto dado"
JELLEJ
1
Sí, este código no funciona
Sourav
2
@JELLEJ Si está obteniendo el problema Uncaught TypeError: el argumento 3 pasó a Magento \ Eav \ Model \ Entity \ Attribute \ OptionManagement :: setOptionValue () debe ser de la cadena de tipo, el objeto dado en la función foreach y luego cambiar $ option-> setLabel ( $ optionLabel); a $ opción-> setLabel ($ etiqueta); en la línea 102
Nadeem0035
11

probado en Magento 2.1.3.

No encontré ninguna forma viable de crear atributos con opciones a la vez. Inicialmente, necesitamos crear un atributo y luego agregarle opciones.

Inyecte la siguiente clase \ Magento \ Eav \ Setup \ EavSetupFactory

 $setup->startSetup();

 /** @var \Magento\Eav\Setup\EavSetup $eavSetup */
 $eavSetup = $this->eavSetupFactory->create(['setup' => $setup]);

Crear nuevo atributo:

$eavSetup->addAttribute(
    'catalog_product',
    $attributeCode,
    [
        'type' => 'varchar',
        'input' => 'select',
        'required' => false,
        ...
    ],
);

Añadir opciones personalizadas.

La función addAttributeno devuelve nada útil que pueda usarse en el futuro. Entonces, después de la creación del atributo, necesitamos recuperar el objeto atributo por nosotros mismos. ¡Importante! Lo necesitamos porque la función solo espera attribute_id, pero no quiere trabajar attribute_code.

En ese caso, necesitamos obtenerlo attribute_idy pasarlo a la función de creación de atributos.

$attributeId = $eavSetup->getAttributeId('catalog_product', 'attribute_code');

Entonces necesitamos generar una matriz de opciones de la forma en que magento espera:

$options = [
        'values' => [
        'sort_order1' => 'title1',
        'sort_order2' => 'title2',
        'sort_order3' => 'title3',
    ],
    'attribute_id' => 'some_id',
];

Como ejemplo:

$options = [
        'values' => [
        '1' => 'Red',
        '2' => 'Yellow',
        '3' => 'Green',
    ],
    'attribute_id' => '32',
];

Y pásalo a funcionar:

$eavSetup->addAttributeOption($options);
zhartaunik
fuente
El tercer parámetro de addAttribute puede tomar el parámetro de matriz ['opción']
DWils
10

El uso de la clase Magento \ Eav \ Setup \ EavSetupFactory o incluso la clase \ Magento \ Catalog \ Setup \ CategorySetupFactory puede provocar el siguiente problema: https://github.com/magento/magento2/issues/4896 .

Las clases que debes usar:

protected $_logger;

protected $_attributeRepository;

protected $_attributeOptionManagement;

protected $_option;

protected $_attributeOptionLabel;

 public function __construct(
    \Psr\Log\LoggerInterface $logger,
    \Magento\Eav\Model\AttributeRepository $attributeRepository,
    \Magento\Eav\Api\AttributeOptionManagementInterface $attributeOptionManagement,
    \Magento\Eav\Api\Data\AttributeOptionLabelInterface $attributeOptionLabel,
    \Magento\Eav\Model\Entity\Attribute\Option $option
  ){
    $this->_logger = $logger;
    $this->_attributeRepository = $attributeRepository;
    $this->_attributeOptionManagement = $attributeOptionManagement;
    $this->_option = $option;
    $this->_attributeOptionLabel = $attributeOptionLabel;
 }

Luego, en su función, haga algo como esto:

 $attribute_id = $this->_attributeRepository->get('catalog_product', 'your_attribute')->getAttributeId();
$options = $this->_attributeOptionManagement->getItems('catalog_product', $attribute_id);
/* if attribute option already exists, remove it */
foreach($options as $option) {
  if ($option->getLabel() == $oldname) {
    $this->_attributeOptionManagement->delete('catalog_product', $attribute_id, $option->getValue());
  }
}

/* new attribute option */
  $this->_option->setValue($name);
  $this->_attributeOptionLabel->setStoreId(0);
  $this->_attributeOptionLabel->setLabel($name);
  $this->_option->setLabel($this->_attributeOptionLabel);
  $this->_option->setStoreLabels([$this->_attributeOptionLabel]);
  $this->_option->setSortOrder(0);
  $this->_option->setIsDefault(false);
  $this->_attributeOptionManagement->add('catalog_product', $attribute_id, $this->_option);
Ruud N.
fuente
1
Gracias, estas en lo correcto. He actualizado mi respuesta en consecuencia. Tenga en cuenta que $attributeOptionLabely $optionson clases ORM; no debes inyectarlos directamente. El enfoque adecuado es inyectar su clase de fábrica, luego crear una instancia según sea necesario. También tenga en cuenta que no está utilizando las interfaces de datos API de manera consistente.
Ryan Hoerr
3
Hola @ Rudd, mira mi comentario sobre la respuesta de Ryan. No desea llamar, $option->setValue()ya que es para un option_idcampo magento interno en la eav_attribute_optionmesa.
quickshiftin
Gracias. Eso es lo que descubrí también. Editaré mi respuesta en consecuencia.
Ruud N.
0

Para Magento 2.3.3, descubrí que puede adoptar el enfoque Magento DevTeam.

  • Agregar parche
bin/magento setup:db-declaration:generate-patch Vendor_Module PatchName
  • Agregar CategorySetupFactory al constructor
public function __construct(
        ModuleDataSetupInterface $moduleDataSetup,
        Factory $configFactory
        CategorySetupFactory $categorySetupFactory
    ) {
        $this->moduleDataSetup = $moduleDataSetup;
        $this->configFactory = $configFactory;
        $this->categorySetupFactory = $categorySetupFactory;
}
  • Añadir atributo en la función apply ()

    public function apply()
    {
        $categorySetup = $this->categorySetupFactory->create(['setup' => $this->moduleDataSetup]);
    
        $categorySetup->addAttribute(
            \Magento\Catalog\Model\Product::ENTITY,
            'custom_layout',
            [
                'type' => 'varchar',
                'label' => 'New Layout',
                'input' => 'select',
                'source' => \Magento\Catalog\Model\Product\Attribute\Source\Layout::class,
                'required' => false,
                'sort_order' => 50,
                'global' => \Magento\Eav\Model\Entity\Attribute\ScopedAttributeInterface::SCOPE_STORE,
                'group' => 'Schedule Design Update',
                'is_used_in_grid' => true,
                'is_visible_in_grid' => false,
                'is_filterable_in_grid' => false
            ]
        );
    }
    
embed0
fuente
uhmm acabo de descubrir que quería agregar esta respuesta a una pregunta diferente. Solo lo viviré aquí y agregaré referencia a esta respuesta allí. Espero que esté bien. Esta es una respuesta parcial a esta pregunta también :)
embed0
-4

Esta NO es una respuesta. Solo una solución alternativa.

Se supone que tiene acceso a Magento Backend usando el navegador y que está en la página de edición de atributos (la URL se ve como admin / catalog / product_attribute / edit / attribute_id / XXX / key ..)

Vaya a la consola del navegador (CTRL + MAYÚS + J en Chrome) y pegue el siguiente código después de cambiar la matriz mimim .

$jq=new jQuery.noConflict();
var mimim=["xxx","yyy","VALUES TO BE ADDED"];
$jq.each(mimim,function(a,b){
$jq("#add_new_option_button").click();
$jq("#manage-options-panel tbody tr:last-child td:nth-child(3) input").val(b);
});

- probado en Magento 2.2.2

Artículo detallado: https://tutes.in/how-to-manage-magento-2-product-attribute-values-options-using-console/

th3pirat3
fuente
1
Esta es una solución terrible a largo plazo. No puede esperar que esos selectores permanezcan igual. Esta es una solución alternativa en el mejor de los casos, si realmente funciona como se esperaba.
domdambrogia
@domdambrogia de acuerdo. Es una solución alternativa.
th3pirat3