Squeezing the extension attributes in Magento 2 #Codehacks
13 Mar, 2018 / 8 MIN readIn this post I will explain how to make correct use of the extension attributes. The first thing is to know what this new Magento 2 functionality is for
Extension attributes are used to add new attributes to models without having to modify neither the class nor the interface of them. This applies to both Magento’s own models and third-party modules. The only requirement is that the model interface that we are going to extend inherits from Magento \Framework \ Api \ ExtensibleDataInterface and its Magento \ Framework \ Model\ AbstractExtensibleModel class.
To explain this new functionality, we are going to be based on an own entity, which we explained in the post Creating a new entity in Magento 2.
It should be noted that the entire module follows the standard PHP recommendations.
Creating models, resources and repositories
For the use of the extension attributes we have 2 options, save these new attributes in the same table of the database or in a different one. The example that we are going to explain is adding the new information in a table.
To do this, we will create exactly one new entity with the same steps as in the post Creating a new entity in Magento 2. In our case, in the InstallSchema.php we will create a field that will store the id of the main model which will be a foreign key of the main model field.
app / code / Interactiv4 / CustomPost / Setup / InstallSchema.php
startSetup(); //Custom post $installer->getConnection()->dropTable($installer->getTable(PostInterface::SCHEMA_TABLE)); $this->installTableCustomPost($installer); $installer->endSetup(); } /** * Create table relations between custom entity and custom post * * @param SchemaSetupInterface $installer * @throws Zend_Db_Exception */ private function installTableCustomPost(SchemaSetupInterface $installer) { $table = $installer->getConnection()->newTable( $installer->getTable(PostInterface::SCHEMA_TABLE) )->addColumn( PostInterface::FIELD_ID, Table::TYPE_INTEGER, null, ['identity' => true, 'unsigned' => true, 'nullable' => false, 'primary' => true], 'Custom post Id' )->addColumn( PostInterface::FIELD_POST_ID, Table::TYPE_INTEGER, null, ['unsigned' => true, 'nullable' => false], 'Post Id' )->addColumn( PostInterface::FIELD_SHORT_DESCRIPTION, Table::TYPE_TEXT, null, ['nullable' => false, 'default' => ''], 'Short description' )->addIndex( $installer->getIdxName( $installer->getTable(PostInterface::SCHEMA_TABLE), [PostInterface::FIELD_POST_ID], AdapterInterface::INDEX_TYPE_UNIQUE ), [PostInterface::FIELD_POST_ID], ['type' => AdapterInterface::INDEX_TYPE_UNIQUE] )->addForeignKey( $installer->getFkName( PostInterface::SCHEMA_TABLE, PostInterface::FIELD_POST_ID, EntityInterface::TABLE, EntityInterface::ID ), PostInterface::FIELD_POST_ID, $installer->getTable(EntityInterface::TABLE), EntityInterface::ID, Table::ACTION_CASCADE )->setComment( 'Custom Post' ); $installer->getConnection()->createTable($table); } }
Creation of extension attributes
To add the extension attributes to an already created model, we must first create a file extension_attributes.xml, in which we will specify the interface of the main model and the fields that we are going to add.
app / code / Interactiv4 / CustomPost / etc / extension_attributes.xml
short_description
In the example above we can see, as we indicated the interface of the main model, the name of the new attribute and its typology (int, string, an object, array …).
The resources parameter is optional and restricts access to the user with a specific permission.
Note the specification of the join, which is optional. At this point, we specify our new table and the fields that are referenced between the main model and ours. This will only work if the joinProcessor of the extension attributes (Magento\Framework\Api\ExtensionAttribute\JoinProcessorInterface) is included in the getList method of the main repository.
app / code / Interactiv4 / Post / Model / EntityRepository.php
entityFactory = $entityFactory;
$this->entityCollectionFactory = $entityCollectionFactory;
$this->entitySearchResultsInterfaceFactory = $entitySearchResultsInterfaceFactory;
$this->collectionProcessor = $collectionProcessor;
$this->extensionAttributesJoinProcessor = $extensionAttributesJoinProcessor;
}
...
/**
* @inheritdoc
*/
public function getList(SearchCriteriaInterface $searchCriteria)
{
/** @var Collection $collection */
$collection = $this->entityCollectionFactory->create();
$this->extensionAttributesJoinProcessor->process($collection, EntityInterface::class);
$this->collectionProcessor->process($searchCriteria, $collection);
/** @var EntitySearchResultsInterface $searchResults */
$searchResults = $this->entitySearchResultsInterfaceFactory->create();
$searchResults->setSearchCriteria($searchCriteria);
$searchResults->setItems($collection->getItems());
$searchResults->setTotalCount($collection->getSize());
return $searchResults;
}
}
Thanks to this joinProcessor we can define in our PostRepository.php a function getListPostByShortDescription () which obtains a list of all the entities of the main model filtering by the value of an extension attributes.
app / code / Interactiv4 / CustomPost / Model / PostRepository.php
resourceCustomPost = $resourceCustomPost; $this->customPostFactory = $customPostFactory; $this->customPostCollectionFactory = $customPostCollectionFactory; $this->customPostSearchResultInterfaceFactory = $customPostSearchResultInterfaceFactory; $this->collectionProcessor = $collectionProcessor; $this->extensionAttributesJoinProcessor = $extensionAttributesJoinProcessor; $this->entityRepository = $entityRepository; $this->filterBuilder = $filterBuilder; $this->searchCriteriaBuilder = $searchCriteriaBuilder; $this->sortOrder = $sortOrder; } /** * @inheritdoc */ public function save(PostInterface $customPost) { $this->resourceCustomPost->save($customPost); return $customPost; } /** * @inheritdoc */ public function getById($customPostId) { return $this->get($customPostId); } /** * @inheritdoc */ public function get($value, $attributeCode = null) { /** @var Post $customPost */ $customPost = $this->customPostFactory->create()->load($value, $attributeCode); if (!$customPost->getId()) { throw new NoSuchEntityException(__('Unable to find custom post')); } return $customPost; } /** * @inheritdoc */ public function delete(PostInterface $customPost) { $customPostId = $customPost->getId(); try { $this->resourceCustomPost->delete($customPost); } catch (Exception $e) { throw new StateException( __('Unable to remove custom post %1', $customPostId) ); } return true; } /** * @inheritdoc */ public function deleteById($customPostId) { $customPost = $this->getById($customPostId); return $this->delete($customPost); } /** * @inheritdoc */ public function getList(SearchCriteriaInterface $searchCriteria) { /** @var Collection $collection */ $collection = $this->customPostCollectionFactory->create(); $this->extensionAttributesJoinProcessor->process($collection, PostInterface::class); $this->collectionProcessor->process($searchCriteria, $collection); /** @var PostSearchResultsInterface $searchResults */ $searchResults = $this->customPostSearchResultInterfaceFactory->create(); $searchResults->setSearchCriteria($searchCriteria); $searchResults->setItems($collection->getItems()); $searchResults->setTotalCount($collection->getSize()); return $searchResults; } /** * @inheritdoc */ public function getListPostByShortDescription($shortDescription) { $filter = $this->filterBuilder ->setField(PostInterface::FIELD_SHORT_DESCRIPTION) ->setValue($shortDescription) ->setConditionType('LIKE') ->create(); /** @var SearchCriteria $searchCriteria */ $searchCriteria = $this->searchCriteriaBuilder->addFilter($filter); $sortOrder = $this->sortOrder->setField(EntityInterface::NAME)->setDirection('asc'); $searchCriteria->setSortOrders([$sortOrder]); $entitySearchResults = $this->entityRepository->getList($searchCriteria); return $entitySearchResults; } }
Thanks to the definition of the file extension_attributes.xml, Magento will generate in the folder var / generation (<Magento 2.2) or generated (> Magento 2.2) an EntityExtensionInterface.php class with the getters and setters of the attributes that we have defined in said xml file .
var / generation /Interactiv4 / Post / Api / Data / EntityExtensionInterface.php or
generated / code / Interactiv4 / Post / Api / Data / EntityExtensionInterface.php
Obtain and store extension attribute information
Plugins
Magento does not store or obtain the information of the extension attributes automatically. We have to obtain it and save it manually by using the plugins in the get (), getList () and save () functions of the main repository.
app / etc / di.xml
app / code / Plugin / EntityRepositoryPlugin.php
entityExtensionFactory = $entityExtensionFactory; $this->customPostFactory = $customPostFactory; $this->customPostRepository = $customPostRepository; } /** * @param EntityRepositoryInterface $subject * @param EntityInterface $result * @return EntityInterface * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function afterGet(EntityRepositoryInterface $subject, EntityInterface $result) { /** @var EntityExtensionInterface $extensionAttributes */ $extensionAttributes = $result->getExtensionAttributes() ?: $this->entityExtensionFactory->create(); try { /** @var PostInterface $customPost */ $customPost = $this->customPostRepository->get($result->getId(), PostInterface::FIELD_POST_ID); } catch (NoSuchEntityException $e) { $result->setExtensionAttributes($extensionAttributes); return $result; } $extensionAttributes->setShortDescription($customPost->getShortDescription()); $result->setExtensionAttributes($extensionAttributes); return $result; } /** * @param EntityRepositoryInterface $subject * @param EntitySearchResultsInterface $entities * @return EntitySearchResultsInterface */ public function afterGetList(EntityRepositoryInterface $subject, EntitySearchResultsInterface $entities) { foreach ($entities->getItems() as $entity) { $this->afterGet($subject, $entity); } return $entities; } /** * @param EntityRepositoryInterface $subject * @param EntityInterface $result * @return EntityInterface * @throws CouldNotSaveException * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function afterSave(EntityRepositoryInterface $subject, EntityInterface $result) { $extensionAttributes = $result->getExtensionAttributes() ?: $this->entityExtensionFactory->create(); if ($extensionAttributes !== null && $extensionAttributes->getShortDescription() !== null ) { /** @var PostInterface $customPost */ try { $customPost = $this->customPostRepository->get($result->getId(), PostInterface::FIELD_POST_ID); } catch (NoSuchEntityException $e) { $customPost = $this->customPostFactory->create(); } $customPost->setPostId($result->getId()); $customPost->setShortDescription($extensionAttributes->getShortDescription()); $this->customPostRepository->save($customPost); } return $result; } }
In this way we get that every time a model or a list is loaded, it contains the additional information of the extension attributes. In turn, each time we store the information of a product, we will also save the additional information.
I hope to have been helpful and if any questions you can leave a comment.
In this link you can download the code of this explanation and in this link the main entity from which we are going to extend.