Magento2 - adicionar programaticamente opções de atributos do produto

32

Qual é a maneira (oficial) correta de adicionar programaticamente a opção de atributo do produto no M2? Por exemplo, para manufactureratributo do produto. Obviamente, a opção existente seria correspondida pelo valor do título "Admin".

werd
fonte

Respostas:

55

Aqui está a abordagem que eu propus para lidar com opções de atributos. Classe 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;
    }
}

Em seguida, na mesma classe ou incluindo-o via injeção de dependência, você pode adicionar ou obter o ID da opção ligando createOrGetId($attributeCode, $label).

Por exemplo, se você injetar My\Module\Helper\Datacomo $this->moduleHelper, poderá chamar:

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

Se 'ABC Corp' for um fabricante existente, ele puxará o ID. Caso contrário, ele será adicionado.

ATUALIZADO 09-09-2016: Per Ruud N., a solução original usou o CatalogSetup, que resultou em um bug começando no Magento 2.1. Esta solução revisada ignora esse modelo, criando a opção e o rótulo explicitamente. Deve funcionar em 2.0+.

Ryan Hoerr
fonte
3
É tão oficial quanto você vai conseguir. Todas as pesquisas e a adição de opções passam pelo Magento principal. Minha classe é apenas um invólucro para os métodos principais que os tornam mais fáceis de usar.
Ryan Hoerr 29/02
1
Oi Ryan, você não deve definir o valor na opção, este é o ID interno que o Magento usa e eu descobri da maneira mais difícil que se você definir o valor para um valor de string com um número inicial como '123 abc corp', isso causa alguns problemas sérios devido à implementação de Magento\Eav\Model\ResourceModel\Entity\Attribute::_processAttributeOptions. Veja você mesmo, se você remover a $option->setValue($label);instrução do seu código, ela salvará a opção; quando você a buscar, o Magento retornará o valor de um incremento automático na eav_attribute_optiontabela.
quickshiftin
2
se eu adicionar isso em uma função foreach, na segunda iteração, receberei o erro "Magento \ Eav \ Model \ Entity \ Attribute \ OptionManagement :: setOptionValue () deve ser do tipo string, objeto fornecido"
JELLEJ
1
Sim, este código não está funcionando
Sourav 23/03
2
@JELLEJ Se você está recebendo um problema TypeError não capturado: o argumento 3 passado para Magento \ Eav \ Model \ Entity \ Attribute \ OptionManagement :: setOptionValue () deve ser da cadeia de caracteres do tipo, objeto fornecido na função foreach, altere $ option-> setLabel ( $ optionLabel); para $ option-> setLabel ($ label); na linha 102
Nadeem0035
11

testado no Magento 2.1.3.

Não encontrei nenhuma maneira viável de criar atributos com opções de uma só vez. Então, inicialmente precisamos criar um atributo e, em seguida, adicionar opções para ele.

Injete a seguinte classe \ Magento \ Eav \ Setup \ EavSetupFactory

 $setup->startSetup();

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

Crie um novo atributo:

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

Adicione opções personalizadas.

A função addAttributenão retorna nada útil que possa ser usado no futuro. Então, após a criação do atributo, precisamos recuperar o objeto de atributo por nós mesmos. !!! Importante Precisamos disso porque a função espera apenas attribute_id, mas não deseja trabalhar attribute_code.

Nesse caso, precisamos obtê-lo attribute_ide passá-lo para atribuir a função de criação.

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

Então precisamos gerar uma matriz de opções da maneira que o magento espera:

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

Como exemplo:

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

E passe-o para funcionar:

$eavSetup->addAttributeOption($options);
zhartaunik
fonte
O terceiro parâmetro do addAttribute pode usar o parâmetro da matriz ['option']
DWils
10

O uso do Magento \ Eav \ Setup \ EavSetupFactory ou mesmo da classe \ Magento \ Catalog \ Setup \ CategorySetupFactory pode levar ao seguinte problema: https://github.com/magento/magento2/issues/4896 .

As classes que você deve 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;
 }

Em sua função, faça algo assim:

 $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.
fonte
1
Obrigado, você está correto. Atualizei minha resposta de acordo. Observe que $attributeOptionLabele $optionsão classes ORM; você não deve injetá-los diretamente. A abordagem adequada é injetar sua classe de fábrica e criar uma instância, conforme necessário. Observe também que você não está usando as interfaces de dados da API de forma consistente.
Ryan Hoerr 9/09/16
3
Oi @ Rudd, veja meu comentário na resposta de Ryan. Você não quer chamar, $option->setValue()pois isso é para um option_idcampo magento interno na eav_attribute_optionmesa.
quickshiftin
Obrigado. Foi o que eu descobri também. Editarei minha resposta de acordo.
Ruud N.
0

Para o Magento 2.3.3, descobri que você pode adotar a abordagem Magento DevTeam.

  • Adicionar patch
bin/magento setup:db-declaration:generate-patch Vendor_Module PatchName
  • Adicionar CategorySetupFactory ao construtor
public function __construct(
        ModuleDataSetupInterface $moduleDataSetup,
        Factory $configFactory
        CategorySetupFactory $categorySetupFactory
    ) {
        $this->moduleDataSetup = $moduleDataSetup;
        $this->configFactory = $configFactory;
        $this->categorySetupFactory = $categorySetupFactory;
}
  • Adicionar atributo na função 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
fonte
uhmm, acabei de descobrir que queria adicionar esta resposta a uma pergunta diferente. Vou vivê-lo aqui e adicionar referência a esta resposta lá. Espero que esteja tudo bem. Esta é parcialmente a resposta para esta pergunta também :)
embed0 04/11
-4

Esta não é uma resposta. Apenas uma solução alternativa.

Ele pressupõe que você tenha acesso ao Magento Backend usando o navegador e esteja na página de atributos de edição (o URL se parece com admin / catalog / product_attribute / edit / attribute_id / XXX / key ..)

Vá para o console do navegador (CTRL + SHIFT + J no chrome) e cole o código a seguir depois de alterar o mimim da matriz .

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

- testado no Magento 2.2.2

Artigo detalhado - https://tutes.in/how-to-manage-magento-2-product-attribute-values-options-using-console/

th3pirat3
fonte
1
Esta é uma terrível solução a longo prazo. Você não pode esperar com segurança que esses seletores permaneçam os mesmos. Esta é uma solução alternativa, na melhor das hipóteses, se realmente funcionar como esperado.
domdambrogia
@domdambrogia concorda. É uma solução alternativa.
th3pirat3