Magento – Magento 2 : extensionpool, read handlers and save handlers

entity-managermagento-2.1magento2

Can anyone explain to me how to use ReadHandler, SaveHandler and the use of the EntityManager/ExtensionPool?

I'm trying to wrap my head around it, but I can't quite understand how to implement it. If I understand correctly, these can be used to perform additional persistent operations on objects, like creating many-to-many relations, like they are used in the CMS module to link the entity to the store.

I'm trying to do the same thing by relating another entity to CMS pages, but I can't seem to get it working. That is, if I'm using this design pattern properly.

Can anyone share some light on this?
I'm sorry I can't share some code at this moment, since I'm not at my work.

EDIT: This is the code I'm currently using:

I've added cms_page_form.xml to my modules' view/adminhtml/ui_component-folder, so I have an extra tab that displays the customer groups:

<?xml version="1.0" encoding="UTF-8"?>
<form xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Ui:etc/ui_configuration.xsd">
    <fieldset name="customer_groups">
        <argument name="data" xsi:type="array">
            <item name="config" xsi:type="array">
                <item name="collapsible" xsi:type="boolean">true</item>
                <item name="label" xsi:type="string" translate="true">Customer Groups</item>
                <item name="sortOrder" xsi:type="number">100</item>
            </item>
        </argument>
        <field name="customer_groups">
            <argument name="data" xsi:type="array">
                <item name="options" xsi:type="object">Magento\Customer\Model\Config\Source\Group\Multiselect</item>
                <item name="config" xsi:type="array">
                    <item name="dataType" xsi:type="string">int</item>
                    <item name="label" xsi:type="string" translate="true">Customer Groups</item>
                    <item name="formElement" xsi:type="string">multiselect</item>
                    <item name="source" xsi:type="string">page</item>
                    <item name="dataScope" xsi:type="string">customer_group</item>
                    <item name="default" xsi:type="string">0</item>
                </item>
            </argument>
        </field>
    </fieldset>
</form>

This works; the tab is rendered and the customer groups are displayed. None are selected by default.

This is my entry in my global di.xml to register my save- and read-handlers. My inspiration was looking at how stores are saved on CMS pages:

<type name="Magento\Framework\EntityManager\Operation\ExtensionPool">
    <arguments>
        <argument name="extensionActions" xsi:type="array">
            <item name="Magento\Cms\Api\Data\PageInterface" xsi:type="array">
                <item name="read" xsi:type="array">
                    <item name="customerGroupReader"
                          xsi:type="string">Vendor\Module\Model\ResourceModel\Page\Relation\CustomerGroup\ReadHandler</item>
                </item>
                <item name="create" xsi:type="array">
                    <item name="customerGroupCreator"
                          xsi:type="string">Vendor\Module\Model\ResourceModel\Page\Relation\CustomerGroup\SaveHandler</item>
                </item>
                <item name="update" xsi:type="array">
                    <item name="customerGroupUpdater"
                          xsi:type="string">Vendor\Module\Model\ResourceModel\Page\Relation\CustomerGroup\SaveHandler</item>
                </item>
            </item>
        </argument>
    </arguments>
</type>

This is my save handler:

<?php

namespace Vendor\Module\Model\ResourceModel\Page\Relation\CustomerGroup;

use Magento\Framework\EntityManager\Operation\ExtensionInterface;
use Magento\Cms\Model\ResourceModel\Page as PageResource;

/**
 * Class SaveHandler
 */
class SaveHandler implements ExtensionInterface
{
    /**
     * @var PageResource
     */
    protected $pageResource;

    /**
     * SaveHandler constructor.
     * @param PageResource $pageResource
     */
    public function __construct(
        PageResource $pageResource
    )
    {
        $this->pageResource = $pageResource;
    }

    /**
     * @param \Magento\Cms\Model\Page $entity
     * @param array $arguments
     */
    public function execute($entity, $arguments = [])
    {
        $connection = $this->pageResource->getConnection();

        // First delete all existing relations:
        $connection->delete('cms_page_customer_group', sprintf('page_id = %d', (int) $entity->getId()));

        // Re-create the relations:
        foreach ($entity->getData('customer_group') as $customerGroupId) {
            $connection->insert('cms_page_customer_group', [
                'page_id' => (int) $entity->getId(),
                'customer_group_id' => (int) $customerGroupId
            ]);
        }

        return $entity;
    }
}

Up to this point everything works. If I select customer groups in the admin, the save handler gets executed and the proper rows are added to the database.

This is my read handler:

<?php

namespace Vendor\Module\Model\ResourceModel\Page\Relation\CustomerGroup;

use Magento\Framework\EntityManager\Operation\ExtensionInterface;
use Magento\Cms\Model\ResourceModel\Page as PageResource;

/**
 * Class ReadHandler
 */
class ReadHandler implements ExtensionInterface
{
    /**
     * @var PageResource
     */
    protected $pageResource;

    /**
     * SaveHandler constructor.
     * @param PageResource $pageResource
     */
    public function __construct(
        PageResource $pageResource
    ) {
        $this->pageResource = $pageResource;
    }

    /**
     * @param \Magento\Cms\Model\Page $entity
     * @param array $arguments
     */
    public function execute($entity, $arguments = [])
    {
        if ($entity->getId()) {
            $connection = $this->pageResource->getConnection();

            $customerGroupIds = $connection
                ->fetchCol(
                    $connection
                        ->select()
                        ->from('cms_page_customer_group', ['customer_group_id'])
                        ->where('page_id = ?', (int)$entity->getId())
                );

            $entity->setData('customer_group', $customerGroupIds);
        }

        return $entity;
    }
}

This is where I'm stuck. The handler works and gets executed. If I perform a var_dump() on $customerGroupIds they are populated with the correct results from the database.

However, in the adminhtml, none of the customer groups in the multiselect are selected. I think I'm really close into solving this one, but I can't figure out what I'm doing wrong. My guts tell me it might have something to do with how I declared the multiselect in cms_page_form.xml, but I'm not sure what it is.

By the way, this is an example of the database scheme:

|page_id|customer_group_id|
|-------|-----------------|
|29     |1                |
|29     |2                |

Any help would be greatly appreciated.

Best Answer

I found an answer on my own question (although I'm not sure if it's the answer):

The Read Handler works as expected and makes sure that the data is added to my model if I load it (for example) using a repository.

So that just left me with the admingrid. After looking through the original cms_page_form.xml I noticed that the data of the form was populated by using Magento\Cms\Model\Page\DataProvider. When I looked at this class I noticed a method called getData() that uses the collection to fetch the entities, not the repository. Probably because the data provider is a general concept that can also be used in grids and stuff (please correct me if I'm wrong).

And the collection did not take the ReadHandler into account. I looked at the collection and the getItems()-method and stuff, but I couldn't find a proper way to add my custom attribute.

So I ended up with writing a plugin for Magento\Cms\Model\Page\DataProvider::getData.

di.xml:

<!--
    Plugin to add customer_group to dataprovider:
-->
<type name="Magento\Cms\Model\Page\DataProvider">
    <plugin name="private_pages_cms_dataprovider"
            type="Vendor\Module\Plugin\Magento\Cms\Model\Page\DataProvider"/>
</type>

And my plugin code:

/**
 * @param \Magento\Cms\Model\Page\DataProvider $subject
 * @param array $result
 * @return array
 */
public function afterGetData(\Magento\Cms\Model\Page\DataProvider $subject, array $result)
{
    foreach ($result as $pageId => &$data) {
        $data['customer_group'] = ...
    }
    return $result;
}

So this works now, but I'm not sure if this is the proper - Magento - way on how to deal with this. Can anyone share some thoughts on this?

Related Topic