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.
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
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();
}
}
Here is a data patch which
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);
}
}
Select
type