1

How can I convert a product attribute type from Text to Dropdown in Magento?

I already have attribute values assigned to many products, so don't want to lose these in the process.

4
  • Use Select type
    – fmsthird
    Commented Feb 4, 2019 at 0:43
  • How and where do I choose "Select" type?
    – John Doe
    Commented Feb 4, 2019 at 16:41
  • Did you resolve your issue? Commented Feb 6, 2019 at 9:57
  • No. It's not that easy. As the option is greyed out. Changing it also implies a lot of database changes. So I've yet to find the solution...
    – John Doe
    Commented Feb 7, 2019 at 12:47

3 Answers 3

3

Magento Won't allow you to change input type of an attribute, and if you somehow change it from database, values of attributes would still not work for you, as Text stores just a value whereas dropdown stores id of each option's value

Instead of working on changing attribute type you can do this:

  • You need to export the data from magento exporter. Like, sku, your_attribute

  • Delete existing attribute

  • Create new one with the same attribute code your_attribute

  • Import the data that you have just exported

1
  • Doesn't this interfere with native magento functions such as ->setColor(
    – John Doe
    Commented Feb 4, 2019 at 16:41
0

You can done this programmatically...

For Magento 2.4.3 Complete code is given below...

Design (TextField) => Wzor(Dropdown)

Department(TextField) => Motyw(Dropdown)

use Magento\Framework\App\Bootstrap;
require '../../app/bootstrap.php';
$bootstrap = Bootstrap::create(BP, $_SERVER);
$objectManager = $bootstrap->getObjectManager();
$state = $objectManager->get('Magento\Framework\App\State');
$state->setAreaCode('frontend');
$objectManager = \Magento\Framework\App\ObjectManager::getInstance();
$action = $objectManager->create('\Magento\Catalog\Model\ResourceModel\Product\Action');
/** @var \Magento\Catalog\Model\ResourceModel\Product\Collection $productcollection */

$productcollection = $objectManager->create('Magento\Catalog\Model\ResourceModel\Product\Collection')
        ->addAttributeToSelect('id');
// Convert these attributes from Text Field to Dropdown
$convertAttributes = [
    'design' => 'wzor',
    'department' => 'motyw',
];
echo "Found " . $productcollection->getSize() . " total products" . PHP_EOL;
foreach($productcollection as $_product)
{
    
    $productId = $_product->getId();
    /** @var \Magento\Catalog\Model\Product $product */
    $product = $objectManager->create('Magento\Catalog\Model\Product')->load($productId);
    $updateAttributes = array();
    
    foreach ($convertAttributes as $from => $to) {
        // Get current text value from product
        $wzor = trim($product->getData($from));
        if ($wzor != "") {
            // Get new attribute
            /** @var \Magento\Catalog\Model\ResourceModel\Eav\Attribute $_attr */
            $_attr = $product->getResource()->getAttribute($to);
            if ($_attr->usesSource()) {
                // Find existing Option for this product's value
                $wzor_id = $_attr->getSource()->getOptionId($wzor);
                // if not found, get or create new Option
                if (!$wzor_id) {
                    getOrSetAttribute($objectManager, $to, $wzor);
                    $_attr = $product->getResource()->getAttribute($to);
                    $_attr->usesSource();
                    // Get new option ID
                    $wzor_id = $_attr->getSource()->getOptionId($wzor);
                }
                echo "Updated attribute " . $_attr->getId() . " `" . $_attr->getAttributeCode() . "` for Product " . $productId . " `" . $product->getSku() . "`" . PHP_EOL;
                $updateAttributes[$to] = $wzor_id;
            }
        }
    }
     
    if(!empty($updateAttributes)){
        try{
            $action->updateAttributes([$productId], $updateAttributes, 0);
        }
        catch(Exception $e){
            echo "Unable to update product " . $productId . PHP_EOL;
            continue;
        }
    }       
    echo "Product ".$productId." Design Pattern updated";
}

function getOrSetAttribute($objectManager, $attibutecode, $values){
    $eavAttributeFactory = $objectManager->get('\Magento\Eav\Model\Entity\AttributeFactory');
    $attributeOptionManagement = $objectManager->get('\Magento\Eav\Api\AttributeOptionManagementInterface');
    
    $magentoAttribute = $eavAttributeFactory->create()->loadByCode('catalog_product', $attibutecode);   
    $attributeCode = $magentoAttribute->getAttributeCode();
    $magentoAttributeOptions = $attributeOptionManagement->getItems(
        'catalog_product',
        $attributeCode
    );
    $attributeOptions = array($values);
    $existingMagentoAttributeOptions = array();
    $newOptions = array();
    $counter = 0;
    foreach($magentoAttributeOptions as $option) {
        if (!$option->getValue()) {
            continue;
        }
        if($option->getLabel() instanceof \Magento\Framework\Phrase) {
            $label = $option->getText();
        } else {
            $label = $option->getLabel();
        }
    
        if($label == '') {
            continue;
        }
    
        $existingMagentoAttributeOptions[] = $label;
        $newOptions['value'][$option->getValue()] = array($label, $label);
        $counter++;
    }
    
    foreach ($attributeOptions as $option) {
        if($option == '') {
            continue;
        }
    
        if(!in_array($option, $existingMagentoAttributeOptions)) {
            $newOptions['value']['option_'.$counter] = array($option, $option);
        }
    
        $counter++;
    }
    
    if(count($newOptions)) {
        $magentoAttribute->setOption($newOptions)->save();
    }
    
}
0

Here is a data patch which

  • converts a backend_type = decimal / frontend_input = text product attribute to a backend_type = int / frontend_input = select
  • works with a DB transaction and rolls everything back on exception
  • saves current data from catalog_product_entity_decimal to a PHP array (RAM)
  • removes existing values for the attribute from catalog_product_entity_decimal table
  • updates the attribute definition (int / select / options and other params - see code)
  • optionally adds the attribute to multiple attribute sets
  • maps old values to new option IDs
  • inserts new option IDs into catalog_product_entity_int table for all affected products

NOTE: In my case I work with Adobe Commerce, so I have the column "row_id", not "entity_id"

Here is the patch:

<?php
declare(strict_types=1);

namespace YourNamespace\ShopSetup\Setup\Patch\Data;

use YourNamespace\ShopSetup\Setup\Service\ProductAttributeSetupService;
use Magento\Eav\Model\Attribute;
use Magento\Eav\Model\AttributeFactory;
use Magento\Eav\Model\Config as EavConfig;
use Magento\Eav\Setup\EavSetup;
use Magento\Eav\Setup\EavSetupFactory;
use Magento\Framework\Setup\ModuleDataSetupInterface;
use Magento\Framework\Setup\Patch\DataPatchInterface;

class ConvertYourAttributeCodeProductAttributeFromTextToSelect implements DataPatchInterface
{
    private const YOUR_ATTRIBUTE_CODE = 'your_attribute_code';

    /**
     * @param ModuleDataSetupInterface $moduleDataSetup
     * @param EavSetupFactory $eavSetupFactory
     * @param EavConfig $eavConfig
     * @param ProductAttributeSetupService $productAttributeSetupService
     * @param AttributeFactory $attributeFactory
     */
    public function __construct(
        private ModuleDataSetupInterface     $moduleDataSetup,
        private EavSetupFactory              $eavSetupFactory,
        private EavConfig                    $eavConfig,
        private ProductAttributeSetupService $productAttributeSetupService,
        private AttributeFactory             $attributeFactory
    ) {
    }

    /**
     * @inheritdoc
     */
    public function apply()
    {
        $connection = $this->moduleDataSetup->getConnection();
        $connection->beginTransaction();
        try {
            // Detect existing values and store them in a PHP array
            $select = $connection->select()
                ->from(['e' => $this->moduleDataSetup->getTable('catalog_product_entity_decimal')])
                ->join(
                    ['a' => $this->moduleDataSetup->getTable('eav_attribute')],
                    'e.attribute_id = a.attribute_id',
                    []
                )
                ->where('a.attribute_code = ?', self::YOUR_ATTRIBUTE_CODE)
                ->reset(\Magento\Framework\DB\Select::COLUMNS)
                ->columns(['e.row_id', 'e.store_id', 'e.value']);

            $existingValues = $connection->fetchAll($select);

            // Extract distinct values for the options
            $distinctValues = array_unique(array_column($existingValues, 'value'));

            // map to integer and then string
            $distinctValues = array_map('intval', $distinctValues);
            $distinctValues = array_map('strval', $distinctValues);

            // map to integer and then string in original array
            foreach ($existingValues as $key => $value) {
                $existingValues[$key]['value'] = (string)(int)($value['value']);
            }

            // Remove existing values from catalog_product_entity_decimal table
            $connection->delete(
                $this->moduleDataSetup->getTable('catalog_product_entity_decimal'),
                [
                    'attribute_id = ?' => $this->eavConfig->getAttribute(
                        'catalog_product',
                        self::YOUR_ATTRIBUTE_CODE
                    )->getAttributeId()
                ]
            );

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

            // Update attribute with new definition
            $eavSetup->addAttribute(
                \Magento\Catalog\Model\Product::ENTITY,
                self::YOUR_ATTRIBUTE_CODE,
                [
                    'type'                    => 'int',
                    'label'                   => 'Your attribute label',
                    'input'                   => 'select',
                    'required'                => false,
                    'user_defined'            => true,
                    'source'                  => \Magento\Eav\Model\Entity\Attribute\Source\Table::class,
                    'visible'                 => true,
                    'system'                  => false,
                    'position'                => 1000,
                    'global'                  => \Magento\Catalog\Model\ResourceModel\Eav\Attribute::SCOPE_GLOBAL,
                    'filterable'              => true,
                    'searchable'              => true,
                    'comparable'              => true,
                    'visible_on_front'        => true,
                    'filterable_in_search'    => true,
                    'used_in_product_listing' => true,
                    'option'                  => ['values' => $distinctValues],
                    'apply_to' => ''
                ]
            );
            $attributeSetNames = [
                'AttributeSetName1'
            ];
            $this->productAttributeSetupService->addAttributeToSet(
                self::YOUR_ATTRIBUTE_CODE,
                $attributeSetNames,
                'GroupName1',
                $eavSetup
            );

            // Reload the attribute (not via eavConfig!) to have fresh data
            /** @var Attribute $attribute */
            $attribute = $this->attributeFactory->createAttribute(Attribute::class);
            $attribute->loadByCode(\Magento\Catalog\Model\Product::ENTITY, self::YOUR_ATTRIBUTE_CODE);

            // Map old values to new option IDs
            $optionValues = $attribute->getSource()->getAllOptions();
            $valueToOptionId = [];
            foreach ($distinctValues as $value) {
                foreach ($optionValues as $option) {
                    if ($option['label'] == $value) {
                        $valueToOptionId[$value] = $option['value'];
                        break;
                    }
                }
            }

            // Set the values into catalog_product_entity_int table
            foreach ($existingValues as $row) {
                $optionId = $valueToOptionId[$row['value']] ?? null;
                if ($optionId !== null) {
                    $connection->insert(
                        $this->moduleDataSetup->getTable('catalog_product_entity_int'),
                        [
                            'attribute_id' => $attribute->getAttributeId(),
                            'store_id'     => $row['store_id'],
                            'row_id'       => $row['row_id'],
                            'value'        => $optionId
                        ]
                    );
                }
            }
            $connection->commit();
        } catch (\Exception $e) {
            $connection->rollBack();
            throw $e;
        }

        return $this;
    }

    /**
     * @inheritdoc
     */
    public static function getDependencies()
    {
        return [CreateProductAttributes::class];
    }

    /**
     * @inheritdoc
     */
    public function getAliases()
    {
        return [];
    }
}

The Setup Service you'll find in this code snippet:

<?php
declare(strict_types=1);

namespace YourNamespace\ShopSetup\Setup\Service;

use Magento\Catalog\Model\Config as CatalogConfig;
use Magento\Catalog\Model\Product;
use Magento\Eav\Model\Entity\Attribute\SetFactory as AttributeSetFactory;
use Magento\Eav\Setup\EavSetup;

class ProductAttributeSetupService
{
    /**
     * @param AttributeSetFactory $attributeSetFactory
     * @param CatalogConfig $catalogConfig
     */
    public function __construct(
        private AttributeSetFactory $attributeSetFactory,
        private CatalogConfig       $catalogConfig
    ) {
    }

    /**
     * Add attribute to attribute set(s)
     *
     * @param string $attributeCode
     * @param array $attributeSetNames
     * @param string $attributeGroup
     * @param EavSetup $eavSetup
     *
     * @return void
     */
    public function addAttributeToSet(
        string   $attributeCode,
        array    $attributeSetNames,
        string   $attributeGroup,
        EavSetup $eavSetup
    ): void {
        $attributeSetIds = $eavSetup->getAllAttributeSetIds(Product::ENTITY);

        foreach ($attributeSetIds as $attributeSetId) {
            $attributeSet = $this->attributeSetFactory->create()->load($attributeSetId);
            if (!$attributeSet->getId()) {
                continue;
            }
            $groupId = $this->catalogConfig->getAttributeGroupId($attributeSetId, $attributeGroup);
            if (!$groupId) {
                $eavSetup->addAttributeGroup(Product::ENTITY, $attributeSetId, $attributeGroup, 100);
            }
            foreach ($attributeSetNames as $attributeSetName) {
                if ($attributeSet->getAttributeSetName() === $attributeSetName) {
                    $eavSetup->addAttributeToGroup(
                        Product::ENTITY,
                        $attributeSetId,
                        $attributeGroup,
                        $attributeCode
                    );
                }
            }
        }
    }

    /**
     * Remove product attribute from system
     *
     * @param string $attributeCode
     * @param EavSetup $eavSetup
     *
     * @return void
     */
    public function removeProductAttribute(string $attributeCode, EavSetup $eavSetup): void
    {
        $eavSetup->removeAttribute(Product::ENTITY, $attributeCode);
    }
}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.