Skip to content

Plugins & Observers

Interception points, DI preferences, and event observers

18 Plugins 1 Observer 11 Preferences

Overview

Magento_ConfigurableProduct uses an extensive plugin architecture to integrate with the catalog, cart, checkout, sales, and inventory systems. The module registers 18 plugins and 1 observer to intercept key operations.

DI Preferences (Interface Bindings)

These preferences define the default implementations for configurable product service contracts:

Interface Implementation
ConfigurableProductManagementInterface Model\ConfigurableProductManagement
LinkManagementInterface Model\LinkManagement
OptionRepositoryInterface Model\OptionRepository
Data\OptionInterface Model\Product\Type\Configurable\Attribute
Data\OptionValueInterface Model\Product\Type\Configurable\OptionValue
Data\ConfigurableItemOptionValueInterface Model\Quote\Item\ConfigurableItemOptionValue
Pricing\Price\PriceResolverInterface Pricing\Price\ConfigurablePriceResolver
Pricing\Price\ConfigurableOptionsProviderInterface Pricing\Price\ConfigurableOptionsProvider
Pricing\Price\LowestPriceOptionsProviderInterface Pricing\Price\LowestPriceOptionsProvider
Model\AttributeOptionProviderInterface Model\AttributeOptionProvider
Pricing\Price\ConfigurableOptionsFilterInterface Pricing\Price\ConfigurableOptionsCompositeFilter

Plugins (Interceptors)

Cart Cart & Quote Plugins

Target Class Plugin Name Plugin Class Sort
CatalogInventory\Model\Quote\Item\QuantityValidator\Initializer\Option configurable_product Model\Quote\Item\QuantityValidator\Initializer\Option\Plugin\ConfigurableProduct 50
Catalog\Model\Product\CartConfiguration configurable_product Model\Product\CartConfiguration\Plugin\Configurable 50
Quote\Model\Quote\Item\Repository configurable Model\Quote\Item\CartItemProcessor (Proxy) -

Catalog Catalog & Product Plugins

Target Class Plugin Name Plugin Class Sort
Catalog\Api\ProductRepositoryInterface configurableProductSaveOptions Model\Plugin\ProductRepositorySave 10
Catalog\Model\Product\Type configurable_output Model\Product\Type\Plugin -
Catalog\Model\Product\TypeTransitionManager configurable_product_transition Model\Product\TypeTransitionManager\Plugin\Configurable 50
Catalog\Model\Entity\Product\Attribute\Group\AttributeMapperInterface configurable_product Model\Entity\Product\Attribute\Group\AttributeMapper\Plugin 50
Catalog\Model\Product product_identities_extender Model\Plugin\ProductIdentitiesExtender -
Catalog\Helper\Product\Configuration configurable_product Helper\Product\Configuration\Plugin 50
ProductVideo\Block\Product\View\Gallery product_video_gallery Block\Plugin\Product\Media\Gallery -

Pricing Price & Validation Plugins

Target Class Plugin Name Plugin Class Sort
Catalog\Model\Product\Attribute\Backend\Price configurable Model\Plugin\PriceBackend 100
Eav\Model\Entity\Attribute\Backend\AbstractBackend ConfigurableProduct::skipValidation Plugin\Model\Attribute\Backend\AttributeValidation -
Catalog\Model\Product\Pricing\Renderer\SalableResolver configurable Plugin\Catalog\Model\Product\Pricing\Renderer\SalableResolver -

Sales Sales & Order Plugins

Target Class Plugin Name Plugin Class Sort
Sales\Model\Order\Admin\Item configurable_product Model\Order\Admin\Item\Plugin\Configurable 50
SalesRule\Model\Rule\Condition\Product apply_rule_on_configurable_children Plugin\SalesRule\Model\Rule\Condition\Product -
Tax\Model\Sales\Total\Quote\CommonTaxCollector apply_tax_class_id Plugin\Tax\Model\Sales\Total\Quote\CommonTaxCollector -

Inventory Stock & Inventory Plugins

Target Class Plugin Name Plugin Class Sort
CatalogInventory\Model\ResourceModel\Stock\Item updateStockChangedAuto Model\Plugin\UpdateStockChangedAuto -

Key Plugin Details

ProductRepositorySave (sortOrder: 10)

Critical plugin that handles saving configurable product options and links:

class ProductRepositorySave
{
    /**
     * Processes configurable_product_options and configurable_product_links
     * extension attributes after product save
     */
    public function afterSave(
        ProductRepositoryInterface $subject,
        ProductInterface $product
    ) {
        if ($product->getTypeId() !== Configurable::TYPE_CODE) {
            return $product;
        }

        $extensionAttributes = $product->getExtensionAttributes();

        // Save configurable options (super attributes)
        if ($extensionAttributes->getConfigurableProductOptions()) {
            $this->saveOptions($product, $extensionAttributes);
        }

        // Save product links (children)
        if ($extensionAttributes->getConfigurableProductLinks()) {
            $this->saveLinks($product, $extensionAttributes);
        }

        return $product;
    }
}

AttributeValidation (skipValidation)

Skips EAV backend validation for child attributes on configurable products:

class AttributeValidation
{
    /**
     * Skip validation for attributes that shouldn't be required on configurable products
     * (e.g., price, weight - these come from children)
     */
    public function aroundValidate(
        AbstractBackend $subject,
        callable $proceed,
        DataObject $object
    ) {
        if ($this->shouldSkipValidation($object, $subject)) {
            return true;
        }
        return $proceed($object);
    }
}

UpdateStockChangedAuto

Triggers configurable parent reindex when child stock changes:

class UpdateStockChangedAuto
{
    /**
     * When child product stock changes, parent configurable needs reindex
     */
    public function afterSave(
        StockItemResource $subject,
        StockItemResource $result,
        AbstractModel $stockItem
    ) {
        $productId = $stockItem->getProductId();
        $parentIds = $this->configurable->getParentIdsByChild($productId);

        foreach ($parentIds as $parentId) {
            $this->indexer->reindexRow($parentId);
        }

        return $result;
    }
}

SalesRule Condition Plugin

Applies cart price rules to configurable children:

class Product
{
    /**
     * When evaluating sales rules, check the simple child product's attributes
     * not the configurable parent's attributes
     */
    public function beforeValidate(
        ProductCondition $subject,
        AbstractModel $model
    ) {
        $product = $model->getProduct();

        if ($product->getTypeId() === Configurable::TYPE_CODE) {
            // Get the actual child product for rule evaluation
            $childProduct = $this->getChildProduct($model);
            $model->setProduct($childProduct);
        }

        return [$model];
    }
}

Event Observers

Magento_ConfigurableProduct registers one observer for admin attribute form handling:

HideUnsupportedAttributeTypes

product_attribute_form_build_main_tab

Hides attributes that cannot be used as super attributes (only 'select' type attributes with global scope can be configurable axes).

class HideUnsupportedAttributeTypes implements ObserverInterface
{
    private $supportedTypes = ['select'];

    public function execute(Observer $observer)
    {
        $form = $observer->getForm();
        $attribute = $observer->getAttribute();

        if (!in_array($attribute->getFrontendInput(), $this->supportedTypes)) {
            // Hide "Use To Create Configurable Product" option
            $configurableField = $form->getElement('is_configurable');
            if ($configurableField) {
                $configurableField->setDisabled(true);
            }
        }
    }
}

Virtual Types

The module defines several virtual types for pricing resolution:

<!-- Price Pool for Configurable Products -->
<virtualType name="Magento\ConfigurableProduct\Pricing\Price\Pool"
             type="Magento\Framework\Pricing\Price\Pool">
    <arguments>
        <argument name="prices">
            <item name="regular_price">ConfigurableRegularPrice</item>
            <item name="final_price">FinalPrice</item>
        </argument>
        <argument name="target">Magento\Catalog\Pricing\Price\Pool</argument>
    </arguments>
</virtualType>

<!-- Final Price Resolver Chain -->
<virtualType name="ConfigurableFinalPriceResolver"
             type="ConfigurablePriceResolver">
    <arguments>
        <argument name="priceResolver">FinalPriceResolver</argument>
    </arguments>
</virtualType>

<!-- Regular Price Resolver Chain -->
<virtualType name="ConfigurableRegularPriceResolver"
             type="ConfigurablePriceResolver">
    <arguments>
        <argument name="priceResolver">RegularPriceResolver</argument>
    </arguments>
</virtualType>