Magento2 | PWA | GraphQL

Usages of Entity Manager in Magento2


The Magento\Framework\EntityManager\EntityManager class was introduced in Magento 2.1 for loading, saving, checking for presence and deleting EAV and Flat objects. To work via EntityManager, you must provide the information about the entity interface in the di.xml file for MetadataPool and for HydratorPool.

We will declare our entities for the “MetadataPool” which will allow us to do what we did before in our Resource Model, that is to say, make the link between our entity (by using the API interface again ) and the database by specifying the table and its primary key.

<type name="Magento\Framework\EntityManager\MetadataPool">
    <arguments>
        <argument name="metadata" xsi:type="array">
            <item name="MyVendor\MyModule\Api\Data\MyEntityInterface" xsi:type="array">
                <item name="entityTableName" xsi:type="string">myvendor_mymodule_myentity_entity</item>
                <item name="identifierField" xsi:type="string">entity_id</item>
                <item name="eavEntityType" xsi:type="string">myvendor_mymodule_myentity</item>
                <item name="entityContext" xsi:type="array">
                   <item name="store" xsi:type="string">Magento\Store\Model\StoreScopeProvider</item>
               </item>
            </item>
        </argument>
    </arguments>
</type>

The Magento\Framework\EntityManager\OperationPool class contains an array of operations that invoke the EntityManager to perform loading, saving, checking for presence, and deleting an object. The operation is executed by the execute method.

Default operations:

checkIfExists — Magento\Framework\EntityManager\Operation\CheckIfExists;

read — Magento\Framework\EntityManager\Operation\Read;

create — Magento\Framework\EntityManager\Operation\Create;

update — Magento\Framework\EntityManager\Operation\Update;

delete — Magento\Framework\EntityManager\Operation\Delete.

The CheckIfExists operation checks by default the presence of a record in the main table using a direct SQL query.

The Read operation performs 3 sub-operations by default:

  • ReadMain
  • ReadAttributes
  • ReadExtensions

The Create operation performs 3 sub-operations by default:

  • CreateMain
  • CreateAttributes
  • CreateExtensions

The Update operation performs 3 sub-operations by default:

  • UpdateMain
  • UpdateAttributes
  • UpdateExtensions

The Delete operation performs 3 sub-operations by default (in the reverse order):

  • DeleteExtensions
  • DeleteAttributes
  • DeleteMain

The operations for working with attributes are located in the Magento\Framework\EntityManager\Operation\AttributePool class.

With the help of overrides on attribute operations, EAV uses the following classes for operations:

  • Magento\Eav\Model\ResourceModel\CreateHandler
  • Magento\Eav\Model\ResourceModel\UpdateHandler
  • Magento\Eav\Model\ResourceModel\ReadHandler

CreateHandler writes the attribute values to the new entity.

UpdateHandler takes a snapshot using ReadSnapshot (which is based on ReadHandler). 

  • It changes the value if the attribute is changed relative to Snapshot and the new value is not empty or the attribute allows null values;
  • deletes if the attribute has been changed relative to Snapshot and the new value is null and the attribute does not allow null values;
  • creates if the attribute is absent in Snapshot and the new value is not null or the attribute allows null values.

ReadHandler executes the reading according to the following algorithm:

1. Gets all the attribute tables for a particular entity_type.
2. Performs the following for each table:
  1.     A select subquery is created from the current table, which requests value and attribute_id;
  2.     a condition is added that entity_id = ID of the requested entity;
  3.     a condition is added for each scope from the context that store_id IN ($scope->getValue());
  4.     sorts by store_id in descending order.

3. Does UNION ALL of all the subqueries.

4. Executes the SQL query.

5. Writes the received values to the $entityData array.

Now we head to the MyVendor\MyModule\Model\ResourceModel\MyEntity Resource Models to override the CRUD methods in order to use the EntityManager. Of course, we must think about adding the EntityManager to our classes.

public function save(AbstractModel $object)
{
    $this->entityManager->save($object);
    return $this;
}

public function load(\Magento\Framework\Model\AbstractModel $object, $value, $field = null)
{
	$this->entityManager->load($object, $value);
  	return $this;
}

public function delete(AbstractModel $object)
{
    $this->entityManager->delete($object);
    return $this;
}

And to finish with the implementation of the EntityManager we will hydrate our objects. But what does that mean? Hydrating an object means giving it the elements necessary to function, such as giving values to its attributes (like what the constructor does when instantiating an object).

<type name="Magento\Framework\EntityManager\HydratorPool">
    <arguments>
        <argument name="hydrators" xsi:type="array">
            <item name="MyVendor\MyModule\Api\Data\MyEntityInterface" xsi:type="string">Magento\Framework\EntityManager\AbstractModelHydrator</item>
        </argument>
    </arguments>
</type>

Now for managing associative tables, we will use "handlers"

The “handlers” are an aspect brought by the EntityManager that will make it possible to manage the associative tables (as in our example with “entity_stores”). These are therefore classes that will be very useful for managing the CRUD on the associative tables using the “Entity Manager”. And so thanks to dependency injection, we avoid managing the CRUD in the Resource Model with the "after/beforeDelete/Save/Update".

The “Handler” classes, extending the “ExtensionInterface” of the Entity Manager, will be called by their “execute” method which will do what you ask when reading, updating, creating or deleting an entity.

Before creating our handlers, we will add a method to our Resource Model (MyVendor\MyModule\Model\ResourceModel\MyEntity) which will allow us to retrieve for an “entityData” all the associated “Stores”. This will be useful for recovering, modifying or saving an “Entity”.

public function lookupStoreIds($entityId)
{ $connection = $this->getConnection(); $entityMetadata = $this->metadataPool->getMetadata(MyEntityInterface::class); $linkField = $entityMetadata->getLinkField(); $select = $connection->select() ->from(['cps' => $this->getTable('myvendor_mymodule_store')], 'store_id') ->join( ['cp' => $this->getMainTable()], 'cps.' . $linkField . ' = cp.' . $linkField, [] ) ->where('cp.' . $entityMetadata->getIdentifierField() . ' = :entity_id'); return $connection->fetchCol($select, ['entity_id' => (int)$entityId]); }

Dependency injection for handlers

It is necessary one last time to make certain additions to the di.xml in order to manage the CRUD and thus use our handlers. To do this, we define the “ExtensionPool” to make the link between the action (create, read, update, delete), our Handler and the model concerned.

<type name="Magento\Framework\EntityManager\Operation\ExtensionPool">
    <arguments>
        <argument name="extensionActions" xsi:type="array">
            <item name="MyVendor\MyModule\Api\Data\MyentityInterface" xsi:type="array">
                <item name="read" xsi:type="array">
                    <item name="storeReader" xsi:type="string">MyVendor\MyModule\Model\ResourceModel\Entity\Relation\Store\ReadHandler</item>
                </item>
                <item name="create" xsi:type="array">
                    <item name="storeCreator" xsi:type="string">MyVendor\MyModule\Model\ResourceModel\Entity\Relation\Store\SaveHandler</item>
                </item>
                <item name="update" xsi:type="array">
                    <item name="storeUpdater" xsi:type="string">MyVendor\MyModule\Model\ResourceModel\Entity\Relation\Store\SaveHandler</item>
                </item>
            </item>                
       </argument>
    </arguments>
</type>

The ReadHandler

The ReadHandler will allow, in this case, to manage the reading of the “EntityData” associated with a “Stores”.

<?php

namespace MyVendor\MyModule\Model\ResourceModel\Entity\Relation\Store;

use MyVendor\MyModule\Model\ResourceModel\Entity;
use Magento\Framework\EntityManager\MetadataPool;
use Magento\Framework\EntityManager\Operation\ExtensionInterface;


class ReadHandler implements ExtensionInterface
{
    protected $metadataPool;

    protected $resourcePage;

    public function __construct(
        MetadataPool $metadataPool,
        Entity $resourcePage
    ) {
        $this->metadataPool = $metadataPool;
        $this->resourcePage = $resourcePage;
    }

    public function execute($entity, $arguments = [])
    {
        if ($entity->getId()) {
            $stores = $this->resourcePage->lookupStoreIds((int)$entity->getId());
            $entity->setData('store_id', $stores);
        }
        return $entity;
    }
}

The SaveHandler

Here the SaveHandler will be used for creating and updating a “EntityData”. In the case where it is an update, we will check if one or more “Entity” have been added or deleted and thus we modify the associative table.

<?php

namespace MyVendor\MyModule\Model\ResourceModel\Entity\Relation\Store;

use Magento\Framework\EntityManager\Operation\ExtensionInterface;
use MyVendor\MyModule\Api\Data\MyEntityInterface;
use MyVendor\MyModule\Model\ResourceModel\Entity;
use Magento\Framework\EntityManager\MetadataPool;

class SaveHandler implements ExtensionInterface
{
    protected $metadataPool;

    protected $resourcePage;

    public function __construct(
        MetadataPool $metadataPool,
        Entity $resourcePage
    ) {
        $this->metadataPool = $metadataPool;
        $this->resourcePage = $resourcePage;
    }

    public function execute($entity, $arguments = [])
    {
        $entityMetadata = $this->metadataPool->getMetadata(MyEntityInterface::class);
        $linkField = $entityMetadata->getLinkField();
        $connection = $entityMetadata->getEntityConnection();
        $oldStores = $this->resourcePage->lookupStoreIds((int)$entity->getId());
        $newStores = $entity->getStores();

        if (!empty($newStores)) {
            $newStores = json_decode($newStores[0]);
        }
        if (is_array($newStores) && is_array($oldStores)) {
            $table = $this->resourcePage->getTable('myvendor_mymodule_store');
            $delete = array_diff($oldStores, $newStores);
            if ($delete) {
                $where = [
                $linkField . ' = ?' => (int)$entity->getData($linkField),
                'store_id IN (?)' => $delete,
                ];
                $connection->delete($table, $where);
            }
            $insert = array_diff($newStores, $oldStores);
            if ($insert) {
                $data = [];
                foreach ($insert as $storeId) {
                    $data[] = [
                    $linkField => (int)$entity->getData($linkField),
                    'store_id' => (int)$storeId
                    ];
                }
                $connection->insertMultiple($table, $data);
            }
        }
        return $entity;
    }
}

After that we can very well imagine a “DeleteHandler” but in our case, if the data model has been built with “DeleteCascade”, it is not necessary to manage the deletion in the associative table.

Tag : Magento2
0 Comments On "Usages of Entity Manager in Magento2"

Back To Top