Magento 2 REST API on the example of a simple module

Greetings to you, dear habravchane! Since I have been developing on the e-commerce platform Magento since 2013, having gained courage and considering that in this area I can call myself, at least, a confident developer, I decided to write my first article on Habré about this system. And I will start with the implementation of the REST API in Magento 2. Out of the box there is a functionality for processing requests and I will try to demonstrate it using the example of a simple module. This article is more intended for those who have already worked with Magenta. And so, who is interested, I ask under the cat.

Outset


I have a very bad imagination, so I came up with the following example: let's imagine that we need to implement a blog, only users from the admin panel can write articles. Periodically, some CRM knocks on us and unloads these articles to itself (why it is not clear, but in this way we will justify using the REST API). To simplify the module, I specifically omitted the implementation of displaying articles on the front end and in the admin panel (you can implement it yourself, I recommend a good article on grids). Only the query processing functionality will be affected here.

Action development


First we create the structure of the module, let's call it AlexPoletaev_Blog (the lack of fantasy has not gone away yet). The module is placed in the app / code directory.

AlexPoletaev / Blog / etc / module.xml
<?xml version="1.0"?> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd"> <module name="AlexPoletaev_Blog" setup_version="1.0.0"/> </config> 


AlexPoletaev / Blog / registration.php
 <?php \Magento\Framework\Component\ComponentRegistrar::register( \Magento\Framework\Component\ComponentRegistrar::MODULE, 'AlexPoletaev_Blog', __DIR__ ); 


These two files are the minimum required for the module.

If we do everything according to Feng Shui, then we need to create service contracts (what is it within the magenta and how it works, you can read here and here ), which we will do:

AlexPoletaev / Blog / Api / Data / PostInterface.php
 <?php namespace AlexPoletaev\Blog\Api\Data; /** * Interface PostInterface * @package AlexPoletaev\Api\Data * @api */ interface PostInterface { /**#@+ * Constants * @var string */ const ID = 'id'; const AUTHOR_ID = 'author_id'; const TITLE = 'title'; const CONTENT = 'content'; const CREATED_AT = 'created_at'; const UPDATED_AT = 'updated_at'; /**#@-*/ /** * @return int */ public function getId(); /** * @param int $id * @return $this */ public function setId($id); /** * @return int */ public function getAuthorId(); /** * @param int $authorId * @return $this */ public function setAuthorId($authorId); /** * @return string */ public function getTitle(); /** * @param string $title * @return $this */ public function setTitle(string $title); /** * @return string */ public function getContent(); /** * @param string $content * @return $this */ public function setContent(string $content); /** * @return string */ public function getCreatedAt(); /** * @param string $createdAt * @return $this */ public function setCreatedAt(string $createdAt); /** * @return string */ public function getUpdatedAt(); /** * @param string $updatedAt * @return $this */ public function setUpdatedAt(string $updatedAt); } 


AlexPoletaev / Blog / Api / PostRepositoryInterface.php
 <?php namespace AlexPoletaev\Blog\Api; use AlexPoletaev\Blog\Api\Data\PostInterface; use Magento\Framework\Api\SearchCriteriaInterface; /** * Interface PostRepositoryInterface * @package AlexPoletaev\Api * @api */ interface PostRepositoryInterface { /** * @param int $id * @return \AlexPoletaev\Blog\Api\Data\PostInterface */ public function get(int $id); /** * @param \Magento\Framework\Api\SearchCriteriaInterface $searchCriteria * @return \AlexPoletaev\Blog\Api\Data\PostSearchResultInterface */ public function getList(SearchCriteriaInterface $searchCriteria); /** * @param \AlexPoletaev\Blog\Api\Data\PostInterface $post * @return \AlexPoletaev\Blog\Api\Data\PostInterface */ public function save(PostInterface $post); /** * @param \AlexPoletaev\Blog\Api\Data\PostInterface $post * @return bool */ public function delete(PostInterface $post); /** * @param int $id * @return bool */ public function deleteById(int $id); } 


Let us examine these two interfaces in more detail. Interface PostInterface displays a table with articles from our blog. Create a table below. Each column from the database should have its own getter and setter in this interface, why this is important - find out later. The PostRepositoryInterface interface provides a standard set of methods for interacting with the database and storing the loaded entities in the cache. The same methods are used for the API. Another important note, the presence of valid PHPDocs in these interfaces is mandatory , since the Magenta, processing the REST request, uses reflection to determine the input parameters and return values ​​in the methods.

Using the install script, create a table where posts from the blog will be stored:

AlexPoletaev / Blog / Setup / InstallSchema.php
 <?php namespace AlexPoletaev\Blog\Setup; use AlexPoletaev\Blog\Api\Data\PostInterface; use AlexPoletaev\Blog\Model\ResourceModel\Post as PostResource; use Magento\Framework\DB\Adapter\AdapterInterface; use Magento\Framework\DB\Ddl\Table; use Magento\Framework\Setup\InstallSchemaInterface; use Magento\Framework\Setup\ModuleContextInterface; use Magento\Framework\Setup\SchemaSetupInterface; use Magento\Security\Setup\InstallSchema as SecurityInstallSchema; /** * Class InstallSchema * @package AlexPoletaev\Blog\Setup */ class InstallSchema implements InstallSchemaInterface { /** * Installs DB schema for a module * * @param SchemaSetupInterface $setup * @param ModuleContextInterface $context * @return void */ public function install(SchemaSetupInterface $setup, ModuleContextInterface $context) { $setup->startSetup(); $table = $setup->getConnection() ->newTable( $setup->getTable(PostResource::TABLE_NAME) ) ->addColumn( PostInterface::ID, Table::TYPE_INTEGER, null, ['identity' => true, 'unsigned' => true, 'nullable' => false, 'primary' => true], 'Post ID' ) ->addColumn( PostInterface::AUTHOR_ID, Table::TYPE_INTEGER, null, ['unsigned' => true, 'nullable' => true,], 'Author ID' ) ->addColumn( PostInterface::TITLE, Table::TYPE_TEXT, 255, [], 'Title' ) ->addColumn( PostInterface::CONTENT, Table::TYPE_TEXT, null, [], 'Content' ) ->addColumn( 'created_at', Table::TYPE_TIMESTAMP, null, ['nullable' => false, 'default' => Table::TIMESTAMP_INIT], 'Creation Time' ) ->addColumn( 'updated_at', Table::TYPE_TIMESTAMP, null, ['nullable' => false, 'default' => Table::TIMESTAMP_INIT_UPDATE], 'Update Time' ) ->addForeignKey( $setup->getFkName( PostResource::TABLE_NAME, PostInterface::AUTHOR_ID, SecurityInstallSchema::ADMIN_USER_DB_TABLE_NAME, 'user_id' ), PostInterface::AUTHOR_ID, $setup->getTable(SecurityInstallSchema::ADMIN_USER_DB_TABLE_NAME), 'user_id', Table::ACTION_SET_NULL ) ->addIndex( $setup->getIdxName( PostResource::TABLE_NAME, [PostInterface::AUTHOR_ID], AdapterInterface::INDEX_TYPE_INDEX ), [PostInterface::AUTHOR_ID], ['type' => AdapterInterface::INDEX_TYPE_INDEX] ) ->setComment('Posts') ; $setup->getConnection()->createTable($table); $setup->endSetup(); } } 


The table will contain the following columns (do not forget, everything is as simple as possible):


Now it is necessary to create a standard set of Model , ResourceModel and Collection classes for Magenta. For what these classes, I will not paint, this topic is extensive and is beyond the scope of this article, who are interested, can google it yourself. In a nutshell, these classes are needed for manipulating entities (articles) from the database. I advise you to read about the Domain Model, Repository and Service Layer patterns.

AlexPoletaev / Blog / Model / Post.php
 <?php namespace AlexPoletaev\Blog\Model; use AlexPoletaev\Blog\Api\Data\PostInterface; use AlexPoletaev\Blog\Model\ResourceModel\Post as PostResource; use Magento\Framework\Model\AbstractModel; /** * Class Post * @package AlexPoletaev\Blog\Model */ class Post extends AbstractModel implements PostInterface { /** * @var string */ protected $_idFieldName = PostInterface::ID; //@codingStandardsIgnoreLine /** * @inheritdoc */ protected function _construct() //@codingStandardsIgnoreLine { $this->_init(PostResource::class); } /** * @return int */ public function getAuthorId() { return $this->getData(PostInterface::AUTHOR_ID); } /** * @param int $authorId * @return $this */ public function setAuthorId($authorId) { $this->setData(PostInterface::AUTHOR_ID, $authorId); return $this; } /** * @return string */ public function getTitle() { return $this->getData(PostInterface::TITLE); } /** * @param string $title * @return $this */ public function setTitle(string $title) { $this->setData(PostInterface::TITLE, $title); return $this; } /** * @return string */ public function getContent() { return $this->getData(PostInterface::CONTENT); } /** * @param string $content * @return $this */ public function setContent(string $content) { $this->setData(PostInterface::CONTENT, $content); return $this; } /** * @return string */ public function getCreatedAt() { return $this->getData(PostInterface::CREATED_AT); } /** * @param string $createdAt * @return $this */ public function setCreatedAt(string $createdAt) { $this->setData(PostInterface::CREATED_AT, $createdAt); return $this; } /** * @return string */ public function getUpdatedAt() { return $this->getData(PostInterface::UPDATED_AT); } /** * @param string $updatedAt * @return $this */ public function setUpdatedAt(string $updatedAt) { $this->setData(PostInterface::UPDATED_AT, $updatedAt); return $this; } } 


AlexPoletaev / Blog / Model / ResourceModel / Post.php
 <?php namespace AlexPoletaev\Blog\Model\ResourceModel; use AlexPoletaev\Blog\Api\Data\PostInterface; use Magento\Framework\Model\ResourceModel\Db\AbstractDb; /** * Class Post * @package AlexPoletaev\Blog\Model\ResourceModel */ class Post extends AbstractDb { /** * @var string */ const TABLE_NAME = 'alex_poletaev_blog_post'; /** * Resource initialization * * @return void */ protected function _construct() //@codingStandardsIgnoreLine { $this->_init(self::TABLE_NAME, PostInterface::ID); } } 


AlexPoletaev / Blog / Model / ResourceModel / Post / Collection.php
 <?php namespace AlexPoletaev\Blog\Model\ResourceModel\Post; use AlexPoletaev\Blog\Model\Post; use AlexPoletaev\Blog\Model\ResourceModel\Post as PostResource; use Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection; /** * Class Collection * @package AlexPoletaev\Blog\Model\ResourceModel\Post */ class Collection extends AbstractCollection { /** * @inheritdoc */ protected function _construct() //@codingStandardsIgnoreLine { $this->_init(Post::class, PostResource::class); } } 


The attentive reader will notice that our model implements the interface created earlier and all its getters and setters.

At the same time we implement the repository and its methods:

AlexPoletaev / Blog / Model / PostRepository.php
 <?php namespace AlexPoletaev\Blog\Model; use AlexPoletaev\Blog\Api\Data\PostInterface; use AlexPoletaev\Blog\Api\Data\PostSearchResultInterface; use AlexPoletaev\Blog\Api\Data\PostSearchResultInterfaceFactory; use AlexPoletaev\Blog\Api\PostRepositoryInterface; use AlexPoletaev\Blog\Model\ResourceModel\Post as PostResource; use AlexPoletaev\Blog\Model\ResourceModel\Post\Collection as PostCollection; use AlexPoletaev\Blog\Model\ResourceModel\Post\CollectionFactory as PostCollectionFactory; use AlexPoletaev\Blog\Model\PostFactory; use Magento\Framework\Api\SearchCriteriaInterface; use Magento\Framework\Exception\NoSuchEntityException; use Magento\Framework\Exception\StateException; /** * Class PostRepository * @package AlexPoletaev\Blog\Model */ class PostRepository implements PostRepositoryInterface { /** * @var array */ private $registry = []; /** * @var PostResource */ private $postResource; /** * @var PostFactory */ private $postFactory; /** * @var PostCollectionFactory */ private $postCollectionFactory; /** * @var PostSearchResultInterfaceFactory */ private $postSearchResultFactory; /** * @param PostResource $postResource * @param PostFactory $postFactory * @param PostCollectionFactory $postCollectionFactory * @param PostSearchResultInterfaceFactory $postSearchResultFactory */ public function __construct( PostResource $postResource, PostFactory $postFactory, PostCollectionFactory $postCollectionFactory, PostSearchResultInterfaceFactory $postSearchResultFactory ) { $this->postResource = $postResource; $this->postFactory = $postFactory; $this->postCollectionFactory = $postCollectionFactory; $this->postSearchResultFactory = $postSearchResultFactory; } /** * @param int $id * @return PostInterface * @throws NoSuchEntityException */ public function get(int $id) { if (!array_key_exists($id, $this->registry)) { $post = $this->postFactory->create(); $this->postResource->load($post, $id); if (!$post->getId()) { throw new NoSuchEntityException(__('Requested post does not exist')); } $this->registry[$id] = $post; } return $this->registry[$id]; } /** * @param \Magento\Framework\Api\SearchCriteriaInterface $searchCriteria * @return \AlexPoletaev\Blog\Api\Data\PostSearchResultInterface */ public function getList(SearchCriteriaInterface $searchCriteria) { /** @var PostCollection $collection */ $collection = $this->postCollectionFactory->create(); foreach ($searchCriteria->getFilterGroups() as $filterGroup) { foreach ($filterGroup->getFilters() as $filter) { $condition = $filter->getConditionType() ? $filter->getConditionType() : 'eq'; $collection->addFieldToFilter($filter->getField(), [$condition => $filter->getValue()]); } } /** @var PostSearchResultInterface $searchResult */ $searchResult = $this->postSearchResultFactory->create(); $searchResult->setSearchCriteria($searchCriteria); $searchResult->setItems($collection->getItems()); $searchResult->setTotalCount($collection->getSize()); return $searchResult; } /** * @param \AlexPoletaev\Blog\Api\Data\PostInterface $post * @return PostInterface * @throws StateException */ public function save(PostInterface $post) { try { /** @var Post $post */ $this->postResource->save($post); $this->registry[$post->getId()] = $this->get($post->getId()); } catch (\Exception $exception) { throw new StateException(__('Unable to save post #%1', $post->getId())); } return $this->registry[$post->getId()]; } /** * @param \AlexPoletaev\Blog\Api\Data\PostInterface $post * @return bool * @throws StateException */ public function delete(PostInterface $post) { try { /** @var Post $post */ $this->postResource->delete($post); unset($this->registry[$post->getId()]); } catch (\Exception $e) { throw new StateException(__('Unable to remove post #%1', $post->getId())); } return true; } /** * @param int $id * @return bool */ public function deleteById(int $id) { return $this->delete($this->get($id)); } } 


The \AlexPoletaev\Blog\Model\PostRepository::getList() should return data of a specific format, so we will need another interface:

AlexPoletaev / Blog / Api / Data / PostSearchResultInterface.php
 <?php namespace AlexPoletaev\Blog\Api\Data; use Magento\Framework\Api\SearchResultsInterface; /** * Interface PostSearchResultInterface * @package AlexPoletaev\Blog\Api\Data */ interface PostSearchResultInterface extends SearchResultsInterface { /** * @return \AlexPoletaev\Blog\Api\Data\PostInterface[] */ public function getItems(); /** * @param \AlexPoletaev\Blog\Api\Data\PostInterface[] $items * @return $this */ public function setItems(array $items); } 


To make it easier to test our module, create two console scripts that add and remove test data from the table:

AlexPoletaev / Blog / Console / Command / DeploySampleDataCommand.php
 <?php namespace AlexPoletaev\Blog\Console\Command; use AlexPoletaev\Blog\Api\PostRepositoryInterface; use AlexPoletaev\Blog\Model\Post; use AlexPoletaev\Blog\Model\PostFactory; use Magento\User\Api\Data\UserInterface; use Magento\User\Model\User; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; /** * Class DeploySampleDataCommand * @package AlexPoletaev\Blog\Console\Command */ class DeploySampleDataCommand extends Command { /**#@+ * @var string */ const ARGUMENT_USERNAME = 'username'; const ARGUMENT_NUMBER_OF_RECORDS = 'number_of_records'; /**#@-*/ /** * @var PostFactory */ private $postFactory; /** * @var PostRepositoryInterface */ private $postRepository; /** * @var UserInterface */ private $user; /** * @param PostFactory $postFactory * @param PostRepositoryInterface $postRepository * @param UserInterface $user */ public function __construct( PostFactory $postFactory, PostRepositoryInterface $postRepository, UserInterface $user ) { parent::__construct(); $this->postFactory = $postFactory; $this->postRepository = $postRepository; $this->user = $user; } /** * @inheritdoc */ protected function configure() { $this->setName('alex_poletaev:blog:deploy_sample_data') ->setDescription('Blog: deploy sample data') ->setDefinition([ new InputArgument( self::ARGUMENT_USERNAME, InputArgument::REQUIRED, 'Username' ), new InputArgument( self::ARGUMENT_NUMBER_OF_RECORDS, InputArgument::OPTIONAL, 'Number of test records' ), ]) ; parent::configure(); } /** * @inheritdoc */ protected function execute(InputInterface $input, OutputInterface $output) { $username = $input->getArgument(self::ARGUMENT_USERNAME); /** @var User $user */ $user = $this->user->loadByUsername($username); if (!$user->getId() && $output->getVerbosity() > 1) { $output->writeln('<error>User is not found</error>'); return null; } $records = $input->getArgument(self::ARGUMENT_NUMBER_OF_RECORDS) ?: 3; for ($i = 1; $i <= (int)$records; $i++) { /** @var Post $post */ $post = $this->postFactory->create(); $post->setAuthorId($user->getId()); $post->setTitle('test title ' . $i); $post->setContent('test content ' . $i); $this->postRepository->save($post); if ($output->getVerbosity() > 1) { $output->writeln('<info>Post with the ID #' . $post->getId() . ' has been created.</info>'); } } } } 


AlexPoletaev / Blog / Console / Command / RemoveSampleDataCommand.php
 <?php namespace AlexPoletaev\Blog\Console\Command; use AlexPoletaev\Blog\Model\ResourceModel\Post as PostResource; use Magento\Framework\App\ResourceConnection; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; /** * Class RemoveSampleDataCommand * @package AlexPoletaev\Blog\Console\Command */ class RemoveSampleDataCommand extends Command { /** * @var ResourceConnection */ private $resourceConnection; /** * @param ResourceConnection $resourceConnection */ public function __construct( ResourceConnection $resourceConnection ) { parent::__construct(); $this->resourceConnection = $resourceConnection; } /** * @inheritdoc */ protected function configure() { $this->setName('alex_poletaev:blog:remove_sample_data') ->setDescription('Blog: remove sample data') ; parent::configure(); } /** * @inheritdoc */ protected function execute(InputInterface $input, OutputInterface $output) { $connection = $this->resourceConnection->getConnection(); $connection->truncateTable($connection->getTableName(PostResource::TABLE_NAME)); if ($output->getVerbosity() > 1) { $output->writeln('<info>Sample data has been successfully removed.</info>'); } } } 


The main feature of Magento 2 is the widespread use of its own implementation of Dependency Injection . In order for Magenta to know which interface corresponds to which implementation, we need to specify these dependencies in the di.xml file. At the same time, we will register the newly created console scripts in this file:

AlexPoletaev / Blog / etc / di.xml
 <?xml version="1.0"?> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> <preference for="AlexPoletaev\Blog\Api\Data\PostInterface" type="AlexPoletaev\Blog\Model\Post"/> <preference for="AlexPoletaev\Blog\Api\PostRepositoryInterface" type="AlexPoletaev\Blog\Model\PostRepository"/> <preference for="AlexPoletaev\Blog\Api\Data\PostSearchResultInterface" type="Magento\Framework\Api\SearchResults" /> <type name="Magento\Framework\Console\CommandList"> <arguments> <argument name="commands" xsi:type="array"> <item name="deploy_sample_data" xsi:type="object">AlexPoletaev\Blog\Console\Command\DeploySampleDataCommand</item> <item name="remove_sample_data" xsi:type="object">AlexPoletaev\Blog\Console\Command\RemoveSampleDataCommand</item> </argument> </arguments> </type> </config> 


Now we register the routes for the REST API, this is done in the webapi.xml file:

AlexPoletaev / Blog / etc / webapi.xml
 <?xml version="1.0"?> <routes xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Webapi:etc/webapi.xsd"> <route url="/V1/blog/posts" method="POST"> <service class="AlexPoletaev\Blog\Api\PostRepositoryInterface" method="save"/> <resources> <resource ref="anonymous"/> </resources> </route> <route url="/V1/blog/posts/:id" method="DELETE"> <service class="AlexPoletaev\Blog\Api\PostRepositoryInterface" method="deleteById"/> <resources> <resource ref="anonymous"/> </resources> </route> <route url="/V1/blog/posts/:id" method="GET"> <service class="AlexPoletaev\Blog\Api\PostRepositoryInterface" method="get"/> <resources> <resource ref="anonymous"/> </resources> </route> <route url="/V1/blog/posts" method="GET"> <service class="AlexPoletaev\Blog\Api\PostRepositoryInterface" method="getList"/> <resources> <resource ref="anonymous"/> </resources> </route> </routes> 


Here we tell Magenta which interface and which method from this interface to use when requesting a specific URL and with a specific http method (POST, GET, etc.). Also, in order to simplify, anonymous resource is used, which allows absolutely anyone to knock on our API, otherwise you need to configure access rights (ACL).

Climax


All further actions imply that you have enabled developer mode. This allows you to avoid unnecessary manipulations with the deployment of static content and DI compilation.

We register our new module, run the command: php bin/magento setup:upgrade .

Check that a new table, alex_poletaev_blog_post , has been created.

Next, load the test data using our custom script:

 php bin/magento -v alex_poletaev:blog:deploy_sample_data admin 

The admin parameter in this script is the username from the admin_user table (you may have it different), in a word, the user from the admin that is listed in the author_id column.

Now you can start testing. For tests, I used Magento 2.2.4, domain http://m224ce.local/ .

One way to test the REST API is to open http://m224ce.local/swagger and use the swagger functionality, but remember, the getList method does not work there correctly. I also checked all the methods with curl, examples:

Get an article with id = 2

 curl -X GET -H "Accept: application/json" "http://m224ce.local/rest/all/V1/blog/posts/2" 

Answer:

 {"id":2,"author_id":1,"title":"test title 2","content":"test content 2","created_at":"2018-06-06 21:35:54","updated_at":"2018-06-06 21:35:54"} 

Get a list of articles that have author_id = 2

 curl -g -X GET -H "Accept: application/json" "http://m224ce.local/rest/all/V1/blog/posts?searchCriteria[filterGroups][0][filters][0][field]=author_id&searchCriteria[filterGroups][0][filters][0][value]=1&searchCriteria[filterGroups][0][filters][0][conditionType]=eq" 

Answer:

 {"items":[{"id":1,"author_id":1,"title":"test title 1","content":"test content 1","created_at":"2018-06-06 21:35:54","updated_at":"2018-06-06 21:35:54"},{"id":2,"author_id":1,"title":"test title 2","content":"test content 2","created_at":"2018-06-06 21:35:54","updated_at":"2018-06-06 21:35:54"},{"id":3,"author_id":1,"title":"test title 3","content":"test content 3","created_at":"2018-06-06 21:35:54","updated_at":"2018-06-06 21:35:54"}],"search_criteria":{"filter_groups":[{"filters":[{"field":"author_id","value":"1","condition_type":"eq"}]}]},"total_count":3} 

Delete article with id = 3

 curl -X DELETE -H "Accept: application/json" "http://m224ce.local/rest/all/V1/blog/posts/3" 

Answer:

 true 

Save the new article

 curl -X POST -H "Content-Type: application/json" -H "Accept: application/json" -d '{"post": {"author_id": 1, "title": "test title 4", "content": "test content 4"}}' "http://m224ce.local/rest/all/V1/blog/posts" 

Answer:

 {"id":4,"author_id":1,"title":"test title 4","content":"test content 4","created_at":"2018-06-06 21:44:24","updated_at":"2018-06-06 21:44:24"} 

Note that for a request with http method POST, you must pass the key post , which actually corresponds to the input parameter ($ post) for the method

 \AlexPoletaev\Blog\Api\PostRepositoryInterface::save() 

Decoupling


For those who are interested in what happens during the request and how Magenta handles it, below I will provide a few references to methods with my comments. If something does not work, then these methods should be debugged first.

The controller responsible for processing the request
\ Magento \ Webapi \ Controller \ Rest :: dispatch ()

Next is called
\ Magento \ Webapi \ Controller \ Rest :: processApiRequest ()

Inside processApiRequest , many other methods are called, but the next most important
\ Magento \ Webapi \ Controller \ Rest \ InputParamsResolver :: resolve ()

\ Magento \ Webapi \ Controller \ Rest \ Router :: match () - a specific route is determined (inside, through the \Magento\Webapi\Model\Rest\Config::getRestRoutes() method, all suitable routes are pulled out from the request). The route object contains all the necessary data to process the request - class, method, access rights, etc.

\ Magento \ Framework \ Webapi \ ServiceInputProcessor :: process ()
- use \Magento\Framework\Reflection\MethodsMap::getMethodParams() , where method parameters are pulled out through reflection

\ Magento \ Framework \ Webapi \ ServiceInputProcessor :: convertValue () - several options for converting an array into a DataObject or an array from a DataObject

\ Magento \ Framework \ Webapi \ ServiceInputProcessor :: _ createFromArray () is a direct conversion where the presence of getters and setters is checked through reflection (remember, I said above that we will return to them?) And that they have a public scope. Next, the object is filled with data via setters.

At the very end, in the method
\ Magento \ Webapi \ Controller \ Rest :: processApiRequest () , via the call_user_func_array method of the repository object is called.

Epilogue


Githaba module repository

You can install in two ways:

1) Through the composer. To do this, add the following object to the repositories array in the composer.json file

 { "type": "git", "url": "https://github.com/alexpoletaev/magento2-blog-demo" } 

Then type the following command in the terminal:

 composer require alexpoletaev/magento2-blog-demo:dev-master 

2) Download the module files and manually copy them to the app/code/AlexPoletaev/Blog directory

Regardless of which method you choose, at the end you need to run an upgrade:

 php bin/magento setup:upgrade 

I hope this article will be useful to someone. If there are any comments, suggestions or questions, then welcome to the comments. Thanks for attention.

Source: https://habr.com/ru/post/413463/


All Articles