Working with service layers/contracts

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:

  • Data interface: A data interface is a read-only presentation of a record, and therefore, this type of interface only has getters to represent a data record.
  • Repository interface: A repository interface gives access to read and write (and delete) data. Every repository interface has the following methods:
    • getList: This returns a list of records based on the (optionally) provided search parameters
    • get: This loads the data from the database and returns a data interface for the specified ID
    • save: This saves the record specified in the data interface
    • delete: This deletes the record specified in the data interface
    • deleteById: This deletes the record specified by the ID
  • Management interface: In a management interface, it is possible to specify special management functions that are not related to the repository.

Using 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.

How to do it…

In this recipe, we will add the option to read, create, or delete a record through a service layer contract:

  1. Create a repository interface where the available commands are declared:

    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);
    }
  2. Create the repository resource model. Here, the defined functions from the interface have the actual code:

    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;
      }
    
    
    }
  3. Create the registry class; this stores the loaded records:

    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]);
      }
    }
  4. Create a data interface; here, the available attributes (getters and setters) are defined:

    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);
    }
  5. Create the data mapping class:

    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);
      }
    }
  6. Create the search results interface; this is used in the 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);
    
    }
  7. Bind the interfaces through 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" />
  8. Add the 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>';
        }
      }
    
    }
  9. Refresh the cache and generated data:
    bin/magento setup:upgrade
    
  10. Access the result of the test URL:

    http://example.com/sample/index/test/

How it works…

The service layer/contract defines the methods and data format through these interfaces.

DemoRepositoryInterface

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" />

DemoInterface

In this interface, the available getters and setters for the object are described. Just like RepositoryInterface, the actual processing of the data is mapped through di.xml to the DataDemo class:

<preference for="GenmatoSampleApiDataDemoInterface" type="GenmatoSampleModelDataDemo" />

See also

In the next recipe, we will see how the getById, deleteById, list, and save methods can be used in your own code.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.235.8