1

TL;DR: Is there a way to declare virtual types and different arguments in di.xml of a module and have that configuration be picked up when the module gets installed without calling bin/magento module:enable Vendor_Module prior to installing it?

Long version.
I have the following task.
I need to import some attributes from a source when a certain module is installed. (Please do not recommend me extensions for importing attributes. This is not the problem itself. It's just an example of a bigger problem.) For this I've created a data install patch that looks kind of like this (it is actually bigger than that, but I left only what's important):

<?php
namespace Vendor\Module\Setup\Patch\Data;

use Magento\Framework\Setup\Patch\DataPatchInterface;
use Magento\Catalog\Model\Product;
use Magento\Eav\Setup\EavSetup;

class ImportAttributes implements DataPatchInterface
{
    /**
     * @var EavSetup
     */
    private $eavSetup;

    /**
     * ImportAttributes constructor.
     * @param EavSetup $eavSetup
     */
    public function __construct(
        EavSetup $eavSetup
    ) {
        $this->eavSetup = $eavSetup;
    }

    /**
     * {@inheritdoc}
     */
    public function apply(): void
    {
        $attributes = $this->getImportData();
        foreach ($attributes as $attributeCode => $attributeData) {
            $this->eavSetup->addAttribute(Product::ENTITY, $attributeCode, $attributeData);
        }
    }

    /**
     * @return array
     */
    private function getImportData(): array
    {
        //get data from somewhere 
        return $attributeArray;
    }
 ....
}

Pretty simple and it works nicely.
Now the problem.
The addAttribute method I'm calling inside the apply method contains this:

    $data = array_replace(
        ['entity_type_id' => $entityTypeId, 'attribute_code' => $code],
        $this->attributeMapper->map($attr, $entityTypeId)
    );

This basically transforms the attribute data to a certain format that is used to insert the attributes in db later.
My data source sends me the attributes already formatted as they should and applying the effects of $this->attributeMapper->map leads to undesired results.

I was able to solve this again easily by creating an attribute mapper (implementation of interface Magento\Eav\Model\Entity\Setup\PropertyMapperInterface) that just returns what it receives.
And I declared that via di.xml

<type name="Vendor\Module\Setup\Patch\Data">
    <arguments>
        <argument name="eavSetup" xsi:type="object">VirtualEavSetup</argument>
    </arguments>
</type>
<virtualType name="VirtualEavSetup" type="Magento\Eav\Setup\EavSetup">
    <arguments>
        <argument name="context" xsi:type="object">VirtualEavSetupContext</argument>
    </arguments>
</virtualType>
<virtualType name="VirtualEavSetupContext" type="Magento\Eav\Model\Entity\Setup\Context">
    <arguments>
        <argument name="attributeMapper" xsi:type="object">Vendor\Module\Model\Attribute\Mapper</argument>
    </arguments>
</virtualType>

This works great, but only if I call php bin/magento module:enable Vendor_Module prior to calling php bin/magento setup:upgrade.
If I don't enable the module first my di.xml file is not taken into account when running the patch.

Is there a way to be able to take into account the di.xml of a module when it gets installed without enabling it first.
I need this because not everyone that installs the module will enable it first and this may lead to strange results.

1 Answer 1

0

I didn't find a solution to use virtual types in patch scripts. Instead I had to do every object instantiation from the patch class itself.
Something like this.

<?php
declare(strict_types=1);

namespace Vendor\Module\Setup\Patch\Data;

use Magento\Framework\Setup\Patch\DataPatchInterface;
use Vendor\Module\Model\Attribute\Mapper;
use Magento\Catalog\Model\Product;
use Magento\Eav\Model\Entity\Setup\Context;
use Magento\Eav\Model\Entity\Setup\ContextFactory;
use Magento\Eav\Setup\EavSetup;
use Magento\Eav\Setup\EavSetupFactory;

class Attribute implements DataPatchInterface
{
    /**
     * @var EavSetupFactory
     */
    private $eavSetupFactory;
    /**
     * @var Mapper
     */
    private $attributeMapper;
    /**
     * @var ContextFactory
     */
    private $eavSetupContextFactory;
    /**
     * @var EavSetup
     */
    private $eavSetup;

    /**
     * Attribute constructor.
     * @param EavSetupFactory $eavSetupFactory
     * @param Mapper $attributeMapper
     * @param ContextFactory $eavSetupContextFactory
     */
    public function __construct(
        EavSetupFactory $eavSetupFactory,
        Mapper $attributeMapper,
        ContextFactory $eavSetupContextFactory
    ) {
        $this->eavSetupFactory = $eavSetupFactory;
        $this->attributeMapper = $attributeMapper;
        $this->eavSetupContextFactory = $eavSetupContextFactory;
    }

    /**
     * @param array $attributes
     * @throws \Magento\Framework\Exception\LocalizedException
     * @throws \Zend_Validate_Exception
     */
    public function apply(array $attributes): void
    {
        $attributes = $this->getImportData();
        foreach ($attributes as $attributeCode => $attributeData) {
            $this->getEavSetup()->addAttribute(Product::ENTITY, $attributeCode, $attributeData);
        }
    }

    /**
     * @return EavSetup
     */
    private function getEavSetup(): EavSetup
    {
        if ($this->eavSetup === null) {
            /** @var Context $context */
            $context = $this->eavSetupContextFactory->create(['attributeMapper' => $this->attributeMapper]);
            $this->eavSetup = $this->eavSetupFactory->create(['context' => $context]);
        }
        return $this->eavSetup;
    }
    /**
     * @return array
     */
    private function getImportData(): array
    {
        //get data from somewhere 
        return $attributeArray;
    }
     ....
}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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