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

  1. Define the API Endpoint
  2. 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
  • Email
  • 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 PathComments
\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::getProcessor
foreach ($this->requestProcessors as $processor) {
if ($processor->canProcess($request)) {
return $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.

Leave a Reply

Your email address will not be published.