A service layer/contract is a fixed interface to get and store data without knowing the underlying layer. It is possible to swap the way the data is stored without changing the service layer.
A service layer consists of three interface types:
getList
: This returns a list of records based on the (optionally) provided search parametersget
: This loads the data from the database and returns a data interface for the specified IDsave
: This saves the record specified in the data interfacedelete
: This deletes the record specified in the data interfacedeleteById
: This deletes the record specified by the IDUsing a service layer also makes it easy to extend your module to access the web API; you only need to add a declaration in the appropriate XML to configure the link from the API command to the right interface.
In this recipe, we will add the option to read, create, or delete a record through a service layer contract:
Api/DemoRepositoryInterface.php
<?php namespace GenmatoSampleApi; interface DemoRepositoryInterface { /** * Save demo list item. * * @api * @param GenmatoSampleApiDataDemoInterface $demo * @return MagentoCustomerApiDataGroupInterface * @throws MagentoFrameworkExceptionInputException If there is a problem with the input * @throws MagentoFrameworkExceptionNoSuchEntityException If a group ID is sent but the group does not exist * @throws MagentoFrameworkExceptionStateInvalidTransitionException *If saving customer group with customer group code that is used by an existing customer group * @throws MagentoFrameworkExceptionLocalizedException */ public function save(GenmatoSampleApiDataDemoInterface $demo); /** * Get demo list item by ID. * * @api * @param int $id * @return GenmatoSampleApiDataDemoInterface * @throws MagentoFrameworkExceptionNoSuchEntityException If $groupId is not found * @throws MagentoFrameworkExceptionLocalizedException */ public function getById($id); /** * Retrieve demo list items. * * The list of demo items can be filtered * * @api * @param MagentoFrameworkApiSearchCriteriaInterface $searchCriteria * @return GenmatoSampleApiDataDemoSearchResultsInterface * @throws MagentoFrameworkExceptionLocalizedException */ public function getList(MagentoFrameworkApiSearchCriteriaInterface $searchCriteria); /** * Delete demo list item. * * @api * @param GenmatoSampleApiDataDemoInterface $demo * @return bool true on success * @throws MagentoFrameworkExceptionStateException If customer group cannot be deleted * @throws MagentoFrameworkExceptionLocalizedException */ public function delete(GenmatoSampleApiDataDemoInterface $demo); /** * Delete demolist by ID. * * @api * @param int $id * @return bool true on success * @throws MagentoFrameworkExceptionNoSuchEntityException * @throws MagentoFrameworkExceptionStateException If customer group cannot be deleted * @throws MagentoFrameworkExceptionLocalizedException */ public function deleteById($id); }
Model/ResourceModel/DemoRepository.php
<?php namespace GenmatoSampleModelResourceModel; use GenmatoSampleModelDemoRegistry; use GenmatoSampleModelDemoFactory; use GenmatoSampleApiDataDemoInterface; use GenmatoSampleApiDataDemoInterfaceFactory; use GenmatoSampleApiDataDemoExtensionInterface; use GenmatoSampleApiDataDemoSearchResultsInterfaceFactory; use GenmatoSampleModelDemo; use GenmatoSampleModelResourceModelDemo as DemoResource; use GenmatoSampleModelResourceModelDemoCollection; use GenmatoSampleApiDemoRepositoryInterface; use MagentoFrameworkExceptionCouldNotDeleteException; use MagentoFrameworkExceptionCouldNotSaveException; use MagentoFrameworkExceptionNoSuchEntityException; use MagentoFrameworkApiSearchCriteriaInterface; use MagentoFrameworkReflectionDataObjectProcessor; use MagentoFrameworkApiExtensionAttributeJoinProcessorInterface; class DemoRepository implements DemoRepositoryInterface { /** @var DemoRegistry */ private $demoRegistry; /** @var DemoFactory */ private $demoFactory; /** @var DemoInterfaceFactory */ private $demoDataFactory; /** @var Demo */ private $demoResourceModel; /** * @var DataObjectProcessor */ private $dataObjectProcessor; /** * @var DemoSearchResultsInterfaceFactory */ protected $searchResultsFactory; /** * @var JoinProcessorInterface */ protected $extensionAttributesJoinProcessor; /** * @param DemoRegistry $demoRegistry * @param DemoFactory $demoFactory * @param DemoInterfaceFactory $demoDataFactory * @param DemoResource $demoResourceModel * @param DataObjectProcessor $dataObjectProcessor * @param DemoSearchResultsInterfaceFactory $searchResultsFactory * @param JoinProcessorInterface $extensionAttributesJoinProcessor */ public function __construct( DemoRegistry $demoRegistry, DemoFactory $demoFactory, DemoInterfaceFactory $demoDataFactory, DemoResource $demoResourceModel, DataObjectProcessor $dataObjectProcessor, DemoSearchResultsInterfaceFactory $searchResultsFactory, JoinProcessorInterface $extensionAttributesJoinProcessor ) { $this->demoRegistry = $demoRegistry; $this->demoFactory = $demoFactory; $this->demoDataFactory = $demoDataFactory; $this->demoResourceModel = $demoResourceModel; $this->dataObjectProcessor = $dataObjectProcessor; $this->searchResultsFactory = $searchResultsFactory; $this->extensionAttributesJoinProcessor = $extensionAttributesJoinProcessor; } /** * {@inheritdoc} */ public function save(DemoInterface $demo) { /** @var Demo $demoModel */ $demoModel = $this->demoFactory->create(); if ($demo->getId()) { $demoModel->load($demo->getId()); } $demoModel ->setTitle($demo->getTitle()) ->setIsVisible($demo->getIsVisible()) ->setIsActive($demo->getIsActive()); try { $demoModel->save(); } catch (Exception $exception) { throw new CouldNotSaveException(__($exception->getMessage())); } return $demoModel->getData(); } /** * {@inheritdoc} */ public function getById($id) { $demoModel = $this->demoRegistry->retrieve($id); $demoDataObject = $this->demoDataFactory->create() ->setId($demoModel->getId()) ->setTitle($demoModel->getTitle()) ->setCreationTime($demoModel->getCreationTime()) ->setUpdateTime($demoModel->getUpdateTime()) ->setIsVisible($demoModel->getIsVisible()) ->setIsActive($demoModel->getIsActive()); return $demoDataObject; } /** * {@inheritdoc} */ public function getList(SearchCriteriaInterface $searchCriteria) { $searchResults = $this->searchResultsFactory->create(); $searchResults->setSearchCriteria($searchCriteria); /** @var Collection $collection */ $collection = $this->demoFactory->create()->getCollection(); foreach ($searchCriteria->getFilterGroups() as $filterGroup) { foreach ($filterGroup->getFilters() as $filter) { $condition = $filter->getConditionType() ?: 'eq'; $collection->addFieldToFilter($filter->getField(), [$condition => $filter->getValue()]); } } $searchResults->setTotalCount($collection->getSize()); $sortOrders = $searchCriteria->getSortOrders(); if ($sortOrders) { /** @var SortOrder $sortOrder */ foreach ($sortOrders as $sortOrder) { $collection->addOrder( $sortOrder->getField(), ($sortOrder->getDirection() == SortOrder::SORT_ASC) ? 'ASC' : 'DESC' ); } } $collection->setCurPage($searchCriteria->getCurrentPage()); $collection->setPageSize($searchCriteria->getPageSize()); /** @var DemoInterface[] $demos */ $demos = []; /** @var Demo $demo */ foreach ($collection as $demo) { /** @var DemoInterface $demoDataObject */ $demoDataObject = $this->demoDataFactory->create() ->setId($demo->getId()) ->setTitle($demo->getTitle()) ->setCreationTime($demo->getCreationTime()) ->setUpdateTime($demo->getUpdateTime()) ->setIsVisible($demo->getIsVisible()) ->setIsActive($demo->getIsActive()); $demos[] = $demoDataObject; } $searchResults->setTotalCount($collection->getSize()); return $searchResults->setItems($demos); } /** * Delete demo list item. * * @param DemoInterface $demo * @return bool true on success * @throws MagentoFrameworkExceptionStateException If customer group cannot be deleted * @throws MagentoFrameworkExceptionLocalizedException */ public function delete(DemoInterface $demo) { return $this->deleteById($demo->getId()); } /** * Delete demo list item by ID. * * @param int $id * @return bool true on success * @throws MagentoFrameworkExceptionNoSuchEntityException * @throws MagentoFrameworkExceptionStateException If customer group cannot be deleted * @throws MagentoFrameworkExceptionLocalizedException */ public function deleteById($id) { $demoModel = $this->demoRegistry->retrieve($id); if ($id <= 0) { throw new MagentoFrameworkExceptionStateException(__('Cannot delete demo item.')); } $demoModel->delete(); $this->demoRegistry->remove($id); return true; } }
Model/DemoRegistry.php
<?php /** * Sample * * @package Genmato_Sample * @author Vladimir Kerkhoff <[email protected]> * @created 2015-12-23 * @copyright Copyright (c) 2015 Genmato BV, https://genmato.com. */ namespace GenmatoSampleModel; use GenmatoSampleApiDataDemoInterface; use MagentoFrameworkExceptionNoSuchEntityException; class DemoRegistry { /** * @var array */ protected $registry = []; /** * @var DemoFactory */ protected $demoFactory; /** * @param DemoFactory $demoFactory */ public function __construct(DemoFactory $demoFactory) { $this->demoFactory = $demoFactory; } /** * Get instance of the Demo Model identified by an id * * @param int $demoId * @return Demo * @throws NoSuchEntityException */ public function retrieve($demoId) { if (isset($this->registry[$demoId])) { return $this->registry[$demoId]; } $demo = $this->demoFactory->create(); $demo->load($demoId); if ($demo->getId() === null || $demo->getId() != $demoId) { throw NoSuchEntityException::singleField(DemoInterface::ID, $demoId); } $this->registry[$demoId] = $demo; return $demo; } /** * Remove an instance of the Demo Model from the registry * * @param int $demoId * @return void */ public function remove($demoId) { unset($this->registry[$demoId]); } }
Api/Data/DemoInterface.php
<?php namespace GenmatoSampleApiData; use GenmatoSampleApiDataDemoExtensionInterface; use MagentoFrameworkApiExtensibleDataInterface; interface DemoInterface extends ExtensibleDataInterface { const ID = 'id'; const TITLE = 'title'; const CREATION_TIME = 'creation_time'; const UPDATE_TIME = 'update_time'; const IS_ACTIVE = 'is_active'; const IS_VISIBLE = 'is_visible'; /** * Get id * * @api * @return int|null */ public function getId(); /** * Set id * * @api * @param int $id * @return $this */ public function setId($id); /** * Get Title * * @api * @return string */ public function getTitle(); /** * Set Title * * @api * @param string $title * @return $this */ public function setTitle($title); /** * Get Is Active * * @api * @return bool */ public function getIsActive(); /** * Set Is Active * * @api * @param bool $isActive * @return $this */ public function setIsActive($isActive); /** * Get Is Visible * * @api * @return bool */ public function getIsVisible(); /** * Set Is Active * * @api * @param bool $isVisible * @return $this */ public function setIsVisible($isVisible); /** * Get creation time * * @api * @return string */ public function getCreationTime(); /** * Set creation time * * @api * @param string $creationTime * @return $this */ public function setCreationTime($creationTime); /** * Get update time * * @api * @return string */ public function getUpdateTime(); /** * Set update time * * @api * @param string $updateTime * @return $this */ public function setUpdateTime($updateTime); /** * Retrieve existing extension attributes object or create a new one. * * @api * @return DemoExtensionInterface|null */ public function getExtensionAttributes(); /** * Set an extension attributes object. * * @api * @param DemoExtensionInterface $extensionAttributes * @return $this */ public function setExtensionAttributes(DemoExtensionInterface $extensionAttributes); }
Model/Data/Demo.php
<?php namespace GenmatoSampleModelData; use MagentoFrameworkApiAbstractExtensibleObject; use GenmatoSampleApiDataDemoInterface; use GenmatoSampleApiDataDemoExtensionInterface; class Demo extends AbstractExtensibleObject implements DemoInterface { /** * Get id * * @return int|null */ public function getId() { return $this->_get(self::ID); } /** * Set id * * @param int $id * @return $this */ public function setId($id) { return $this->setData(self::ID, $id); } /** * Get code * * @return string */ public function getTitle() { return $this->_get(self::TITLE); } /** * Set code * * @param string $title * @return $this */ public function setTitle($title) { return $this->setData(self::TITLE, $title); } /** * Get Is Active * * @return bool */ public function getIsActive() { return $this->_get(self::IS_ACTIVE); } /** * Set Is Active * * @param bool $isActive * @return $this */ public function setIsActive($isActive) { return $this->setData(self::IS_ACTIVE, $isActive); } /** * Get Is Visible * * @return bool */ public function getIsVisible() { return $this->_get(self::IS_VISIBLE); } /** * Set Is Active * * @param bool $isVisible * @return $this */ public function setIsVisible($isVisible) { return $this->setData(self::IS_VISIBLE, $isVisible); } /** * Get creation time * * @return string */ public function getCreationTime() { return $this->_get(self::CREATION_TIME); } /** * Set creation time * * @param string $creationTime * @return $this */ public function setCreationTime($creationTime) { return $this->setData(self::CREATION_TIME, $creationTime); } /** * Get update time * * @return string */ public function getUpdateTime() { return $this->_get(self::UPDATE_TIME); } /** * Set update time * * @param string $updateTime * @return $this */ public function setUpdateTime($updateTime) { return $this->setData(self::UPDATE_TIME, $updateTime); } /** * {@inheritdoc} * * @return DemoExtensionInterface|null */ public function getExtensionAttributes() { return $this->_getExtensionAttributes(); } /** * {@inheritdoc} * * @param DemoExtensionInterface $extensionAttributes * @return $this */ public function setExtensionAttributes(DemoExtensionInterface $extensionAttributes) { return $this->_setExtensionAttributes($extensionAttributes); } }
getList
command:Api/Data/DemoSearchResultsInterface.php
<?php namespace GenmatoSampleApiData; use GenmatoSampleApiDataDemoInterface; use MagentoFrameworkApiSearchResultsInterface; interface DemoSearchResultsInterface extends SearchResultsInterface { /** * Get demo item list. * * @api * @return DemoInterface[] */ public function getItems(); /** * Set demo item list. * * @api * @param DemoInterface[] $items * @return $this */ public function setItems(array $items); }
di.xml
by adding the following lines:etc/di.xml
<preference for="GenmatoSampleApiDemoRepositoryInterface" type="GenmatoSampleModelResourceModelDemoRepository" /> <preference for="GenmatoSampleApiDataDemoInterface" type="GenmatoSampleModelDataDemo" /> <preference for="GenmatoSampleApiDataDemoSearchResultsInterface" type="MagentoFrameworkApiSearchResults" />
Test
controller; here, we test the working of the service layer: (This is optional.)Controller/Index/Test.php
<?php namespace GenmatoSampleControllerIndex; use MagentoFrameworkAppActionAction; use MagentoFrameworkAppActionContext; use MagentoFrameworkViewResultPageFactory; use MagentoFrameworkApiSearchCriteriaBuilder; use GenmatoSampleApiDemoRepositoryInterface; use GenmatoSampleModelDataDemoFactory; use GenmatoSampleApiDataDemoInterface; class Test extends Action { /** * @var PageFactory */ private $resultPageFactory; /** @var DemoRepositoryInterface */ private $demoRepository; /** @var DemoFactory */ private $demo; /** * @var SearchCriteriaBuilder */ private $searchCriteriaBuilder; /** * @param Context $context * @param PageFactory $resultPageFactory * @param DemoRepositoryInterface $demoRepository * @param SearchCriteriaBuilder $searchCriteriaBuilder * @param DemoFactory $demoFactory */ public function __construct( Context $context, PageFactory $resultPageFactory, DemoRepositoryInterface $demoRepository, SearchCriteriaBuilder $searchCriteriaBuilder, DemoFactory $demoFactory ) { $this->demoRepository = $demoRepository; $this->searchCriteriaBuilder = $searchCriteriaBuilder; $this->demo = $demoFactory; parent::__construct($context); } /** * Renders Sample */ public function execute() { echo '<pre>'; // Create new record through service layer/contract /** @var DemoInterface $demoRecord */ $demoRecord = $this->demo->create(); $demoRecord->setIsActive(1) ->setIsVisible(1) ->setTitle('Test through Service Layer'); $demo = $this->demoRepository->save($demoRecord); print_r($demo); // Get list of available records $searchCriteria = $this->searchCriteriaBuilder->create(); $searchResult = $this->demoRepository->getList($searchCriteria); foreach ($searchResult->getItems() as $item) { echo $item->getId().' => '.$item->getTitle().'<br>'; } } }
bin/magento setup:upgrade
http://example.com/sample/index/test/
The service layer/contract defines the methods and data format through these interfaces.
This interface describes the available methods, input, and output that is expected. The actual logic for the methods is done by the ModelResourceModel DemoRepository
class. In di.xml
, there is a preference created that will use DemoRepository
instead of the Interface
class:
<preference for="GenmatoSampleApiDemoRepositoryInterface" type="GenmatoSampleModelResourceModelDemoRepository" />
3.144.91.47