24
How to save a Magento 2 Product Attribute programmatically
Saving an attribute value from a dropdown value can be hassle, especially when you want to use it in integration tests, but don't know how the ID of an attribute value, but you know the string value, because it's a custom defined attribute and not programmatically.
The tldr code:
/**
* @var Magento\Catalog\Model\Product $product
* @var Magento\Catalog\Api\ProductRepositoryInterface $productRepository
*/
$product = $productRepository->get('simple', false, null, true);
$product->setData('attribute_code', $value);
$productRepository->save($product);
Now for some longer code, which is the story of what led me to needing this, is simple integration tests with values that are defined by the end user, but needs to be implemented in the code for values(long live real life scenarios)
To save your own attribute values that can be selected I suggest you copy the code from Magento/Catalog/_files/products_with_layered_navigation_attribute.php and modify with the values you need.
To avoid magic strings we'll use a class to hold our constants.
namespace Tschallacka\Example\Attributes;
class Attributes
{
const MY_ATTRIBUTE_NAME = 'my_supplier';
}
class Suppliers
{
const SUPPLIER_A = 'Supplier a';
const SUPPLIER_B = 'Supplier B';
const SUPPLIER_C = 'C Supplier inc.';
}
We make a helper to get the value from the possible dropdown values
namespace Tschallacka\Example\Helper;
use Magento\Eav\Api\AttributeRepositoryInterface;
use Magento\Framework\App\Helper\AbstractHelper;
use Magento\Catalog\Model\Product;
use Magento\Framework\App\Helper\Context;
class AttributeHelper extends AbstractHelper
{
static $archive = [];
public function __construct(
Context $context,
AttributeRepositoryInterface $attribute_repository
) {
$this->attribute_repository = $attribute_repository;
parent::__construct($context);
}
public function getAttributeValueForStringValue($attribute_code, $search_string)
{
$options = $this->getAttributeOptions($attribute_code);
if(array_key_exists($search_string, $options)) {
return $options[$search_string];
}
}
public function getAttributeOptions($attribute_code)
{
if(!array_key_exists($attribute_code, self::$archive)) {
self::$archive[$attribute_code] = array_reduce(
$this->attribute_repository
->get(Product::ENTITY, $attribute_code)
->getSource()
->getAllOptions(false))
)
, function ($collector, $attribute) {
$collector[$attribute['label']] = $attribute['value'];
return $collector;
}, []);
}
return self::$archive[$attribute_code];
}
}
Then in the integration test or where else you need it, you can use it as follows in a data fixture(or your own class with the appropriate classes provided by the injection) to save the attribute value.
use PHPUnit\Framework\TestCase;
use Magento\TestFramework\ObjectManager;
use Magento\Catalog\Api\ProductRepositoryInterface;
use Magento\Eav\Api\AttributeRepositoryInterface;
use Tschallacka\Helper\AttributeHelper;
use Tschallacka\Attributes\Attributes;
use Tschallacka\Attributes\Suppliers;
class MyIntegrationTest extends TestCase
{
public static function modifySimpleProduct()
{
/** @var ObjectManager $objectManager */
$objectManager = Bootstrap::getObjectManager();
/** @var ProductRepositoryInterface $productRepository */
$productRepository = $objectManager->create(ProductRepositoryInterface::class);
$attributeRepository = $objectManager->create(AttributeRepositoryInterface::class);
/** @var AttributeHelper $attributeHelper */
$attributeHelper = $objectManager->create(AttributeHelper::class);
$option = $attributeHelper->getAttributeValueForStringValue(Attributes::MY_ATTRIBUTE_NAME, Suppliers::SUPPLIER_C);
/** @var Product $product */
$product = $productRepository->get('simple', false, null, true);
$product->setData(Attributes::MY_ATTRIBUTE_NAME, $option);
$productRepository->save($product);
}
}
24