Magento 2 – Declaring Modifier PHP Fatal Error: Null Given in $modifiers

data-providermagento2ui-formuicomponent

I get this php error:

PHP Fatal error: Uncaught TypeError: Argument 2 passed to Magento\Ui\DataProvider\Modifier\Pool::__construct() must be of the type array, null given, called in /vendor/magento/framework/ObjectManager/Factory/AbstractFactory.php on line 111 and defined in /vendor/magento/module-ui/DataProvider/Modifier/Pool.php:34

Vendor/Module/etc/adminhtml/di.xml

<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
        <virtualType name="Vendor\Module\Ui\DataProvider\Item\Form\Modifier\Pool" type="Magento\Ui\DataProvider\Modifier\Pool">
            <arguments>
                <argument name="modifiers" xsi:type="array">
                    <item name="test-modifier" xsi:type="array">
                        <item name="class" xsi:type="string">Vendor\Module\Ui\DataProvider\Item\Form\Modifier\TestModifier</item>
                        <item name="sortOrder" xsi:type="number">20</item>
                    </item>
                </argument>
            </arguments>
        </virtualType>
    </config>

Vendor/Module/Ui/DataProvider/Item/Form/DataProvider.php

    <?php
namespace Vendor\Module\Ui\DataProvider\Item\Form;

    use Vendor\Module\Model\ResourceModel\Item\CollectionFactory;
    use Magento\Ui\DataProvider\AbstractDataProvider;
    use Magento\Ui\DataProvider\Modifier\ModifierInterface;
    use Magento\Ui\DataProvider\Modifier\PoolInterface;

    class DataProvider extends AbstractDataProvider
    { 
        /**
         * @var PoolInterface
         */
        private $pool;

        /**
         * Construct
         *
         * @param string $name
         * @param string $primaryFieldName
         * @param string $requestFieldName
         * @param CollectionFactory $collection
         * @param PoolInterface poolInterface
         * @param array $meta
         * @param array $data
         */
        public function __construct(
            $name,
            $primaryFieldName,
            $requestFieldName,
            CollectionFactory $collection,
            PoolInterface $poolInterface,
            array $meta = [],
            array $data = []
        ) {
            parent::__construct($name, $primaryFieldName, $requestFieldName, $meta, $data);
            $this->collection = $collectionFactory->create();
            $this->pool = $poolInterface;
        }

        public function getData()
        {
            /** @var ModifierInterface $modifier */
            foreach ($this->pool->getModifiersInstances() as $modifier) {
                $this->data = $modifier->modifyData($this->data);
            }

            return $this->data;
        }

        public function getMeta()
        {
            $meta = parent::getMeta();

            /** @var ModifierInterface $modifier */
            foreach ($this->pool->getModifiersInstances() as $modifier) {
                $meta = $modifier->modifyMeta($meta);
            }

            return $meta;
        }
    }

Vendor/Module/Ui/DataProvider/Item/Form/Modifier/TestModifier.php

    <?php
namespace Vendor\Module\Ui\DataProvider\Item\Form\Modifier;

    use Magento\Ui\DataProvider\Modifier\ModifierInterface;

    class TestModifier implements ModifierInterface
    {

        public function modifyMeta(array $meta)
        {
            $meta['test_fieldset_name'] = [
                'arguments' => [
                    'data' => [
                        'config' => [
                            'label' => __('Label For Fieldset'),
                            'sortOrder' => 50,
                            'collapsible' => true
                        ]
                    ]
                ],
                'children' => [
                    'test_field_name' => [
                        'arguments' => [
                            'data' => [
                                'config' => [
                                    'formElement' => 'select',
                                    'componentType' => 'field',
                                    'options' => [
                                        ['value' => 'test_value_1', 'label' => 'Test Value 1'],
                                        ['value' => 'test_value_2', 'label' => 'Test Value 2'],
                                        ['value' => 'test_value_3', 'label' => 'Test Value 3'],
                                    ],
                                    'visible' => 1,
                                    'required' => 1,
                                    'label' => __('Label For Element')
                                ]
                            ]
                        ]
                    ]
                ]
            ];
            return $this->meta;
        }

        /**
         * {@inheritdoc}
         */
        public function modifyData(array $data)
        {
            return $data;
        }
    }

Vendor/Module/view/adminhtml/ui_component/item_form.xml

<form xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Ui:etc/ui_configuration.xsd">
    <argument name="data" xsi:type="array">
        <item name="js_config" xsi:type="array">
            <item name="provider" xsi:type="string">item_form.item_form_data_source</item>
            <item name="deps" xsi:type="string">item_form.item_form_data_source</item>
        </item>
        <item name="config" xsi:type="array">
            <item name="namespace" xsi:type="string">item_form</item>
            <item name="dataScope" xsi:type="string">data</item>
            <item name="template" xsi:type="string">templates/form/collapsible</item>
            <item name="childrenFromMeta" xsi:type="boolean">true</item>
        </item>
        <item name="label" xsi:type="string" translate="true">Item Form</item>
        <item name="buttons" xsi:type="array">
            <item name="back" xsi:type="string">Vendor\Module\Block\Adminhtml\Item\Edit\BackButton</item>
            <item name="delete" xsi:type="string">Vendor\Module\Block\Adminhtml\Item\Edit\DeleteButton</item>
            <item name="save" xsi:type="string">Vendor\Module\Block\Adminhtml\Item\Edit\SaveButton</item>
            <item name="save_and_continue" xsi:type="string">Vendor\Module\Block\Adminhtml\Item\Edit\SaveAndContinueButton</item>
        </item>
    </argument>
    <dataSource name="item_form_data_source">
        <argument name="dataProvider" xsi:type="configurableObject">
            <argument name="class" xsi:type="string">Vendor\Module\Ui\DataProvider\Item\Form\DataProvider</argument>
            <argument name="name" xsi:type="string">item_form_data_source</argument>
            <argument name="primaryFieldName" xsi:type="string">itemID</argument>
            <argument name="requestFieldName" xsi:type="string">itemID</argument>
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="submit_url" xsi:type="url" path="*/*/save"/>
                    <item name="validate_url" xsi:type="url" path="*/*/validate"/>
                </item>
            </argument>
        </argument>
        <argument name="data" xsi:type="array">
            <item name="js_config" xsi:type="array">
                <item name="component" xsi:type="string">Magento_Ui/js/form/provider</item>
            </item>
        </argument>
    </dataSource>
</form>

Is the code above correct? Has anyone an idea why I get the PHP Fatal Error "null" given on $modifiers?

EDIT:
I've removed the generated/code folder. The result is the following exception: Missing required argument $modifiers of Magento\Ui\DataProvider\Modifier\Pool.

And when I run php bin/magento setup:di:compile I get the PHP Fatal Error: null given in $modifiers as mentioned above.

EDIT 2:
It doesn't matter if the argument within di.xml is called modifiers or anything else, behavior stays the same as described above.

Best Answer

Finally, this worked for me:

changing Vendor/Module/etc/adminhtml/di.xml to

<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
        <virtualType name="Vendor\Module\Ui\DataProvider\Item\Form\Modifier\Pool" type="Magento\Ui\DataProvider\Modifier\Pool">
            <arguments>
                <argument name="modifiers" xsi:type="array">
                    <item name="test-modifier" xsi:type="array">
                        <item name="class" xsi:type="string">Vendor\Module\Ui\DataProvider\Item\Form\Modifier\TestModifier</item>
                        <item name="sortOrder" xsi:type="number">20</item>
                    </item>
                </argument>
            </arguments>
        </virtualType>
        <type name="Vendor\Module\Ui\DataProvider\Item\Form\DataProvider">
        <arguments>
            <argument name="pool" xsi:type="object">Vendor\Module\Ui\DataProvider\Item\Form\Modifier\Pool</argument>
        </arguments>
    </type>
    </config>

and changing Vendor/Module/Ui/DataProvider/Item/Form/DataProvider.php to

<?php
namespace Vendor\Module\Ui\DataProvider\Item\Form;

    use Vendor\Module\Model\ResourceModel\Item\CollectionFactory;
    use Magento\Ui\DataProvider\AbstractDataProvider;
    use Magento\Ui\DataProvider\Modifier\ModifierInterface;
    use Magento\Ui\DataProvider\Modifier\PoolInterface;

    class DataProvider extends AbstractDataProvider
    { 
        /**
         * @var PoolInterface
         */
        private $pool;

        /**
         * Construct
         *
         * @param string $name
         * @param string $primaryFieldName
         * @param string $requestFieldName
         * @param CollectionFactory $collection
         * @param PoolInterface $pool
         * @param array $meta
         * @param array $data
         */
        public function __construct(
            $name,
            $primaryFieldName,
            $requestFieldName,
            CollectionFactory $collection,
            PoolInterface $pool,
            array $meta = [],
            array $data = []
        ) {
            parent::__construct($name, $primaryFieldName, $requestFieldName, $meta, $data);
            $this->collection = $collection->create();
            $this->pool = $pool;
        }

        public function getData()
        {
            /** @var ModifierInterface $modifier */
            foreach ($this->pool->getModifiersInstances() as $modifier) {
                $this->data = $modifier->modifyData($this->data);
            }

            return $this->data;
        }

        public function getMeta()
        {
            $meta = parent::getMeta();

            /** @var ModifierInterface $modifier */
            foreach ($this->pool->getModifiersInstances() as $modifier) {
                $meta = $modifier->modifyMeta($meta);
            }

            return $meta;
        }
    }

Explanation: The pool now can be injected correctly to the DataProvider because of the correct naming of the arguments.