Attributes

What an attribute definition contains: code, backend type, input type, and the optional source, backend and frontend models.

Syntax$this->eavConfig->getAttribute('catalog_product', 'color')->getBackendType()

An attribute is one named field, defined once and shared by every product. The definition lives in two tables. eav_attribute holds the parts every entity type shares. catalog_eav_attribute holds the catalog-only parts, such as is_filterable and used_in_product_listing. The second one is called the additional table, and the entity type's row in eav_entity_type names it.

The four things that matter

attribute_code is the identifier you use in code. It starts with a letter and is at most 60 characters, because a flat-table column name cannot exceed 64 in MySQL.

backend_type is one of static, varchar, int, decimal, text or datetime. It decides which value table holds the data.

frontend_input is the control the admin form renders: text, textarea, texteditor, select, multiselect, boolean, date, price, media_image.

Three optional classes attach behaviour:

  • Source model supplies the options for a select or multiselect.
  • Backend model validates and transforms the value around load, save and delete.
  • Frontend model formats the value for display.

Scope

is_global is 1 for Global, 0 for Store View and 2 for Website. A store-view attribute keeps one value row per store_id; a global one keeps a single row with store_id 0.

After editing a source or backend class, run bin/magento cache:clean eav. A brand new class also needs bin/magento setup:di:compile before it resolves in production mode.

Example

Example · php
<?php
// app/code/Vendor/Catalog/Model/Product/Attribute/Source/Origin.php
declare(strict_types=1);

namespace Vendor\Catalog\Model\Product\Attribute\Source;

use Magento\Eav\Model\Entity\Attribute\Source\AbstractSource;

class Origin extends AbstractSource
{
    private const OPTIONS = [
        'pt' => 'Portugal',
        'it' => 'Italy',
        'jp' => 'Japan',
    ];

    /**
     * @return array<int, array<string, mixed>>
     */
    public function getAllOptions()
    {
        if ($this->_options === null) {
            $this->_options = [];

            foreach (self::OPTIONS as $value => $label) {
                $this->_options[] = ['value' => $value, 'label' => __($label)];
            }
        }

        return $this->_options;
    }
}

// Referenced from a data patch as:
//     'source' => Origin::class
// which is shorthand for the same string written out:
//     'source' => 'Vendor\Catalog\Model\Product\Attribute\Source\Origin'

When to use it

  • Deciding between a source model and stored options for a dropdown field.
  • Enforcing a format rule on a product field everywhere it can be saved.
  • Working out why a store view shows the default value instead of its own.

More examples

A backend model that validates on save

Attached with 'backend' => Isbn::class when the attribute is created. The check then runs wherever the product is saved: the admin form, an import, the REST API, a cron job. Reach for a backend model instead of a plugin or an observer when the rule belongs to the field rather than to one screen. The parameter stays untyped because the parent method is untyped; widening it would break the inherited signature.

Example · php
<?php
// app/code/Vendor/Catalog/Model/Product/Attribute/Backend/Isbn.php
declare(strict_types=1);

namespace Vendor\Catalog\Model\Product\Attribute\Backend;

use Magento\Eav\Model\Entity\Attribute\Backend\AbstractBackend;
use Magento\Framework\Exception\LocalizedException;

class Isbn extends AbstractBackend
{
    /**
     * @param \Magento\Framework\DataObject $object
     * @return bool
     * @throws LocalizedException
     */
    public function validate($object)
    {
        $code = $this->getAttribute()->getAttributeCode();
        $value = $object->getData($code);

        if ($value !== null && $value !== '' && preg_match('/^\d{13}$/', (string) $value) !== 1) {
            throw new LocalizedException(
                __('The ISBN must be 13 digits.')
            );
        }

        return parent::validate($object);
    }
}

Read a definition at runtime

ProductAttributeRepositoryInterface::get() takes the attribute code, not an id, and throws NoSuchEntityException when the code does not exist. Use this when you are debugging someone else's attribute and need to know whether a value will arrive as a string, an option id or a decimal before you write code against it.

Example · php
<?php
// app/code/Vendor/Catalog/Model/AttributeReport.php
declare(strict_types=1);

namespace Vendor\Catalog\Model;

use Magento\Catalog\Api\ProductAttributeRepositoryInterface;

class AttributeReport
{
    public function __construct(
        private readonly ProductAttributeRepositoryInterface $attributeRepository
    ) {
    }

    /**
     * @return array<string, mixed>
     */
    public function describe(string $attributeCode): array
    {
        $attribute = $this->attributeRepository->get($attributeCode);

        return [
            'code' => $attribute->getAttributeCode(),
            'backend_type' => $attribute->getBackendType(),
            'frontend_input' => $attribute->getFrontendInput(),
            'source_model' => $attribute->getSourceModel(),
            'required' => (bool) $attribute->getIsRequired(),
            'scope_global' => (int) $attribute->getIsGlobal() === 1,
        ];
    }
}

Discussion

  • Be the first to comment on this lesson.