Magento 2 - How Rest API Response data is Generated
In this blog we will look in to how Magento generates the data for the rest API.
When you think of building a Rest API the thing that comes in our mind is generally the below.
- Url Endpoint
- Input parameters
- Json Return Data
and the implementation would be some think like the below
- Create A Route / Controller
- Call the respective Model to get all the data
- Build an array with the relevant data and return as json Response
$userID = "Get the Request Parameter Value";
$user = $userInstane->getUser($userID);
$userAddresses = $userAddressInstane->getAddresses($userID);
$data = [
'name' => $user->getName(),
'email' => $user->getEmail(),
'addresses' => $userAddresses
]
echo json_decode($data);
With Magento 2, Things are done in a little different manner.
Instead of we gathering the data and echoing the data, We just return a Object.
This returned object will be a usually a API Data Interface where we will have getters and setters method. When this object is returned Magento calls all the getter methods automatically and returns the data.
For ex, In the below class we have 4 getters, So in the output we will have upto 4 data depends upon what data is set.
So to look more in to how Magento 2 does this, First lets create a simple web api.
Creating a Web API Involves the Below Steps
- Define the API Endpoint
- Create API Service Class, Usually a repository or the Management Class
Define the API endpoint
The Api endpoint is defined in the etc/webapi.xml file
Here we can would define the below details
- API Url along with its parameter
- The Service Class which will be initiated on this api call
- ACL
- Api Method
When this rest api url is called, Magento will call the getUser method where we create the instance of the API interface and return the object.
<route url="/V1/mosesrest/:userId/user" method="GET">
<service class="Moses\Rest\Api\UserManagementInterface" method="getUser"/>
<resources>
<resource ref="anonymous"/>
</resources>
</route>
Create Api Interface
Every rest api will return an object of an api interface. When you create the getter methods of this api class we need to pay attention to 2 things
Method Arguments
If you need to pass a argument in your api call, Method arguments are used. i.e the method arguments will be automatically mapped to api parameters
In this url "/V1/mosesrest/:userId/user" the data that is passed as :userId will be available as $userId in the getUser method.
public function getUser(int $userId)
Method return Value and Importance of Doc Block
Every getter method should define the comment block with the @return param such as
/**
* @return string
*/
Using the reflection class, Magento uses this definition to decide what kind of data that needs to be returned for that specific getter.
Lets consider the below class that contains 4 getters and setters
- Name
- UserGroups
- Id
<?php
namespace Moses\Rest\Api\Data;
/**
* @api
* @since 100.0.2
*/
interface UserInterface
{
const KEY_ID = 'id';
const KEY_NAME = 'name';
const KEY_EMAIL = 'email';
const KEY_USER_GROUPS = 'user_groups';
/**
* @return int|null
*/
public function getId();
/**
* @return string|null
*/
public function getEmail();
/**
* @return string
*/
public function getName();
/**
* @return array
*/
public function getUserGroups();
/**
* @param $name
* @return $this
*/
public function setName($name);
/**
* @param $email
* @return $this
*/
public function setEmail($email);
/**
* @param $userGroups
* @return $this
*/
public function setUserGroups($userGroups);
/**
* Set User id
*
* @param int $id
* @return $this
*/
public function setId($id);
}
The method that is mapped in the webapi xml creates the object of this class and returns the data to the end user.
Once the api "/rest/V1/mosesrest/1/user" is called, This class \Moses\User\Model\UserManagement::getUser will get called.
Consider we are not setting any data, but just creating the api instance object and returning the object and lets see how the output is
// Just returning the API interface object not setting any value
public function getUser(int $userId)
{
$user = $this->userInterfaceFactory->create();
return $user;
}
Output:
{
"name": null,
"user_groups": null
}
If you notice the output here, Though our object contains four properties we are getting only two properties. The email and id property are missing.
The reason is, because of the DocBlock Definition, In our block definition for getEmail and getId methods we defined "@return int|null" which indicates that this method can return null value also in that case the in the api response output we will not have this keys if the value is not set.
But there may be a scenario where we need to return the email property as null if we are not setting it, So to do it we just need to change the doc block of the Email Method @return as the below
/**
* @return string
*/
public function getEmail();
The | null is removed.
After changing the doc value, you need to clear the reflection cache
php bin/magento cache:clean reflection
Let have a look closely on how this data is processed
The API Processing starts with
File Path | Comments |
\Magento\Framework\App\Http::launch $result = $frontController->dispatch($this->_request); | Run application |
\Magento\Webapi\Controller\Rest::dispatch $processor = $this->requestProcessorPool->getProcessor($this->_request); | Handle REST request Based on request decide is it schema request or API request and process accordingly. |
\Magento\Webapi\Controller\Rest\RequestProcessorPool::getProcessorforeach ($this->requestProcessors as $processor) { Here the Processer for the api is identified based on the api method, type etc | |
For our call, the below method will get initiated \Magento\Webapi\Controller\Rest\SynchronousRequestProcessor::process The Input parameters of the URL are resolved here and the respective service class is identified | |
\Magento\Webapi\Controller\Rest\SynchronousRequestProcessor::process Once the service API class is identified the respective method getUser is called | $service = Moses\User\Model\UserManagement $$serviceMethodName = getUser $inputParams = {array} [1] 0 = {int} 1 The input parameter "1" is passed in the api url |
// $service = Moses\Rest\Api\UserManagementInterface // $serviceMethodName = getUser $outputData = call_user_func_array([$service, $serviceMethodName], $inputParams); At this point the $outputData contains the data what exactly we set. We set only id and user_groups and the output is as expected. But if you see the final api response we see name:null is returned. We didnt set email and name but why only name is returned as null and not email ? This is because of the respective methods return doctype declaration and this is handled in the below method. ServiceOutputProcessor::process \Magento\Framework\Webapi\ServiceInputProcessor::process Then the Service Method is called | |
The above $outputData is passed to \Magento\Framework\Webapi\ServiceOutputProcessor::process $outputData = $this->serviceOutputProcessor->process( $outputData, $serviceClassName, $serviceMethodName ); | This method does the following Converts the incoming data into scalar or an array of scalars format. If the data provided is null, then an empty array is returned. Otherwise, if the data is an object, it is assumed to be a Data Object and converted to an associative array with keys representing the properties of the Data Object. Nested Data Objects are also converted. If the data provided is itself an array, then we iterate through the contents and convert each piece individually. In our case the Data is Object which is further processed. |
return $this->processDataObject( $this->dataObjectProcessor->buildOutputDataArray($data, $type) ); | In our case the Data is Object which is further processed. |
\Magento\Framework\Reflection\DataObjectProcessor::buildOutputDataArray | |
Here we do 2 checks If the method value is NULL If !$isMethodReturnValueRequired In our case, The getEmail will be NULL so let look in to $isMethodReturnValueRequired | |
Based on just the DOC Block you can see that the isRequired flag is set to true or false and this value determines if we need to return the key "email" in the return value or not. |