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 Genmato\Sample\Api;
interface DemoRepositoryInterface
{
/**
* Save demo list item.
*
* @api
* @param \Genmato\Sample\Api\Data\DemoInterface $demo
* @return \Magento\Customer\Api\Data\GroupInterface
* @throws \Magento\Framework\Exception\InputException If there is a problem with the input
* @throws \Magento\Framework\Exception\NoSuchEntityException If a group ID is sent but the group does not exist
* @throws \Magento\Framework\Exception\State\InvalidTransitionException
*If saving customer group with customer group code that is used by an existing customer group
* @throws \Magento\Framework\Exception\LocalizedException
*/
public function save(\Genmato\Sample\Api\Data\DemoInterface $demo);
/**
* Get demo list item by ID.
*
* @api
* @param int $id
* @return \Genmato\Sample\Api\Data\DemoInterface
* @throws \Magento\Framework\Exception\NoSuchEntityException If $groupId is not found
* @throws \Magento\Framework\Exception\LocalizedException
*/
public function getById($id);
/**
* Retrieve demo list items.
*
* The list of demo items can be filtered
*
* @api
* @param \Magento\Framework\Api\SearchCriteriaInterface $searchCriteria
* @return \Genmato\Sample\Api\Data\DemoSearchResultsInterface
* @throws \Magento\Framework\Exception\LocalizedException
*/
public function getList(\Magento\Framework\Api\SearchCriteriaInterface $searchCriteria);
/**
* Delete demo list item.
*
* @api
* @param \Genmato\Sample\Api\Data\DemoInterface $demo
* @return bool true on success
* @throws \Magento\Framework\Exception\StateException If customer group cannot be deleted
* @throws \Magento\Framework\Exception\LocalizedException
*/
public function delete(\Genmato\Sample\Api\Data\DemoInterface $demo);
/**
* Delete demolist by ID.
*
* @api
* @param int $id
* @return bool true on success
* @throws \Magento\Framework\Exception\NoSuchEntityException
* @throws \Magento\Framework\Exception\StateException If customer group cannot be deleted
* @throws \Magento\Framework\Exception\LocalizedException
*/
public function deleteById($id);
}Model/ResourceModel/DemoRepository.php
<?php
namespace Genmato\Sample\Model\ResourceModel;
use Genmato\Sample\Model\DemoRegistry;
use Genmato\Sample\Model\DemoFactory;
use Genmato\Sample\Api\Data\DemoInterface;
use Genmato\Sample\Api\Data\DemoInterfaceFactory;
use Genmato\Sample\Api\Data\DemoExtensionInterface;
use Genmato\Sample\Api\Data\DemoSearchResultsInterfaceFactory;
use Genmato\Sample\Model\Demo;
use Genmato\Sample\Model\ResourceModel\Demo as DemoResource;
use Genmato\Sample\Model\ResourceModel\Demo\Collection;
use Genmato\Sample\Api\DemoRepositoryInterface;
use Magento\Framework\Exception\CouldNotDeleteException;
use Magento\Framework\Exception\CouldNotSaveException;
use Magento\Framework\Exception\NoSuchEntityException;
use Magento\Framework\Api\SearchCriteriaInterface;
use Magento\Framework\Reflection\DataObjectProcessor;
use Magento\Framework\Api\ExtensionAttribute\JoinProcessorInterface;
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 \Magento\Framework\Exception\StateException If customer group cannot be deleted
* @throws \Magento\Framework\Exception\LocalizedException
*/
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 \Magento\Framework\Exception\NoSuchEntityException
* @throws \Magento\Framework\Exception\StateException If customer group cannot be deleted
* @throws \Magento\Framework\Exception\LocalizedException
*/
public function deleteById($id)
{
$demoModel = $this->demoRegistry->retrieve($id);
if ($id <= 0) {
throw new \Magento\Framework\Exception\StateException(__('Cannot delete demo item.'));
}
$demoModel->delete();
$this->demoRegistry->remove($id);
return true;
}
}Model/DemoRegistry.php
<?php
/**
* Sample
*
* @package Genmato_Sample
* @author Vladimir Kerkhoff <support@genmato.com>
* @created 2015-12-23
* @copyright Copyright (c) 2015 Genmato BV, https://genmato.com.
*/
namespace Genmato\Sample\Model;
use Genmato\Sample\Api\Data\DemoInterface;
use Magento\Framework\Exception\NoSuchEntityException;
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 Genmato\Sample\Api\Data;
use Genmato\Sample\Api\Data\DemoExtensionInterface;
use Magento\Framework\Api\ExtensibleDataInterface;
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 Genmato\Sample\Model\Data;
use Magento\Framework\Api\AbstractExtensibleObject;
use Genmato\Sample\Api\Data\DemoInterface;
use Genmato\Sample\Api\Data\DemoExtensionInterface;
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 Genmato\Sample\Api\Data;
use Genmato\Sample\Api\Data\DemoInterface;
use Magento\Framework\Api\SearchResultsInterface;
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="Genmato\Sample\Api\DemoRepositoryInterface"
type="Genmato\Sample\Model\ResourceModel\DemoRepository" />
<preference for="Genmato\Sample\Api\Data\DemoInterface" type="Genmato\Sample\Model\Data\Demo" />
<preference for="Genmato\Sample\Api\Data\DemoSearchResultsInterface"
type="Magento\Framework\Api\SearchResults" />Test controller; here, we test the working of the service layer: (This is optional.)Controller/Index/Test.php
<?php
namespace Genmato\Sample\Controller\Index;
use Magento\Framework\App\Action\Action;
use Magento\Framework\App\Action\Context;
use Magento\Framework\View\Result\PageFactory;
use Magento\Framework\Api\SearchCriteriaBuilder;
use Genmato\Sample\Api\DemoRepositoryInterface;
use Genmato\Sample\Model\Data\DemoFactory;
use Genmato\Sample\Api\Data\DemoInterface;
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 Model\ResourceModel\ DemoRepository class. In di.xml, there is a preference created that will use DemoRepository instead of the Interface class:
<preference for="Genmato\Sample\Api\DemoRepositoryInterface" type="Genmato\Sample\Model\ResourceModel\DemoRepository" />