REST API with Magento 2 | News | PHPro
What's new in our world

REST API with Magento 2

18 December 2016

On October the second I was at the second Yireo Magento 2 Seminar at Utrecht (Netherlands). There was a short talk about "APIs in Magento 2" presented by Andra Lungu, Magento developer at BitBull_IT.

At this moment we are building a very big Magento 2 shop for one of our customers. For this project we have to integrate with several external systems/platforms. For example: we must send order information to their CRM at regular basis during the order flow. To create a PDF of the order / invoice, we need the relationnumber of the customer to print in the header of the document. Because the relationnumber isn't stored in Magento we must set up a REST API to retrieve this number from CRM.

I will give you a general overview how to build your own web API with Magento 2 based on code examples.

Protocols and authentication

Magento 2 supports SOAP and REST with authentication types OAuth-basedToken-based and Session-based. The request body for REST can be JSON or XML (use the correct Content-Type header: application/json or application/xml).

For now, I show you how to start with Token Based authentication. To get an admin token you can use the V1/integration/admin/token API endpoint.

curl -X POST "http://test.phpro.local:8080/rest/V1/integration/admin/token" -H "Content-Type:application/json" -d '{"username":"apiuser", "password":"apiuser123"}'

A successful request returns a response body with the token, as follows: 45462b4c237b57e4gxbfke10epwm87w9

For most web API calls, you supply this token in the Authorization request header with the Bearer HTTP authorization scheme to prove your identity. The token never expires, but it can be revoked.

  • Make sure the user exists and has access to at least one resource/role.
  • The tokens are saved in the database in the oauth_token table.

Authorization: access the resources/roles

All accounts (Token-based) and integrations (OAuth-based) are assigned to resources/roles that they have access to. The API module of Magento 2 checks that any call has authorization to perform the request. For example: if authorized for the Magento_Sales::sales resource, they can make a GET /V1/orders/:id call. The resources are defined in acl.xml.

In our project we define a new resource/role in app/code/Phpro/Sales/etc/acl.xml.

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Acl/etc/acl.xsd">
    <acl>
        <resources>
            <resource id="Magento_Backend::admin">
                <resource id="Magento_Sales::sales">
                    <resource id="Phpro_Sales::relationnumber" title="Update relation number">
                    </resource>
                </resource>
            </resource>
        </resources>
    </acl>
</config>

Configure a web API endpoint

To configure a web API for a service, you define XML elements and attributes in a webapi.xml configuration file.

In our project we define the web API service in app/code/Phpro/Sales/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/order/:orderId/relationnumber" method="POST">
        <service class="Phpro\Sales\Api\RestOrderManagementInterface" method="updateRelationNumber"/>
        <resources>
            <resource ref="Phpro_Sales::relationnumber" />
        </resources>
    </route>
</routes>

Magento dynamically makes the service method available using the web API. It's very important the service class is formatted in a very specific way. Magento uses reflection to automatically create these PHP classes and converts the submitted JSON or XML into corresponding method arguments. Conversely, if an object is returned from one of these methods, Magento also converts the object to a JSON or XML response. To do this conversion, all methods exposed by the web API must follow these rules:

  • Parameters must be defined in the doc block as * @param type $paramName
  • Return type must be defined in the doc block as * @return type
  • Valid scalar types: boolean (bool), string (str), integer (int), float and double.
  • Valid object types include a fully qualified class name or a fully qualified interface name
    Any parameters or return values of type array can be denoted by following any of the previous types by an empty set of square brackets []. For example * @param string[] $types
  • Optional request paramaters can be set as follows: public function methodName($arg1, $arg2 = 0, $arg3 = null);

In \Magento\Webapi\Controller\Rest\InputParamsResolver::resolve() you can see the logic which is responsible for processing and resolving the input parameters.

 The web API module

Below the stucture of the Phpro_Sales web API module.

 

Registration of the module

app/code/Phrpo/Sales/registration.php

<?php
\Magento\Framework\Component\ComponentRegistrar::register(
    \Magento\Framework\Component\ComponentRegistrar::MODULE,
    'Phpro_Sales',
    __DIR__
);

app/code/Phrpo/Sales/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="Phpro_Sales" setup_version="0.0.1" />
</config>

app/code/Phrpo/Sales/Setup/InstallSchema.php

<?php
namespace Phpro\Sales\Setup;
 
use Magento\Framework\Setup\InstallSchemaInterface;
use Magento\Framework\Setup\ModuleContextInterface;
use Magento\Framework\Setup\SchemaSetupInterface;
 
/**
 * Class InstallSchema
 * @package Phpro\Sales\Setup
 */
class InstallSchema implements InstallSchemaInterface
{
 
    /**
     * @param SchemaSetupInterface   $installer
     * @param ModuleContextInterface $context
     */
    public function install(SchemaSetupInterface $installer, ModuleContextInterface $context)
    {
        $installer->startSetup();
 
        $salesOrderTable = $installer->getTable('sales_order');
        $installer->getConnection()->addColumn(
            $salesOrderTable,
            'relation_number',
            'varchar(25) null default \'\''
        );
 
        $installer->endSetup();
    }
}

Configure the web API

See above app/code/Phpro/Sales/etc/webapi.xml and app/code/Phpro/Sales/etc/acl.xml.

Create a new interface: app/code/Phpro/Sales/Api/RestOrderManagementInterface.php

<?php
namespace Phpro\Sales\Api;
 
/**
 * Interface RestOrderManagementInterface
 * @package Phpro\Sales\Api
 */
interface RestOrderManagementInterface
{
    /**
     * @param integer $orderId
     * @param string  $relationNumber
     *
     * @return boolean
     * @throws \Exception
     */
    public function updateRelationNumber($orderId, $relationNumber);
}

 Define the implemenation of the method: app/code/Phpro/Sales/Model/RestOrderManagement.php

<?php
namespace Phpro\Sales\Model;
 
use Magento\Framework\Exception\InputException;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Exception\NoSuchEntityException;
use Magento\Sales\Model\OrderRepository;
use Phpro\Sales\Api\RestOrderManagementInterface;
 
/**
 * Class RestOrderManagement
 * @package Phpro\Sales\Model
 */
class RestOrderManagement implements RestOrderManagementInterface
{
    /**
     * @var OrderRepository
     */
    private $orderRepository;
 
    /**
     * @param OrderRepository $orderRepository
     */
    public function __construct(OrderRepository $orderRepository) {
        $this->orderRepository = $orderRepository;
    }
 
    /**
     * @inheritdoc
     */
    public function updateRelationNumber($orderId, $relationNumber)
    {
        try {
            $order = $this->orderRepository->get($orderId);
            $order->setRelationNumber($relationNumber);
            $this->orderRepository->save($order);
        } catch (NoSuchEntityException $e) {
            throw new InputException(__('Could not retrieve order ID %1.', $orderId));
        } catch (\Exception $e) {
            throw new LocalizedException(__('Could not update relation number for order ID %1.', $orderId));
        }
 
        return true;
    }
}

Don't forget to set a preference for the interface in app/code/Phpro/Sales/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="Phpro\Sales\Api\RestOrderManagementInterface" type="Phpro\Sales\Model\RestOrderManagement" />
</config>

Run bin/magento setup:upgrade and bin/magento cache:flush to register and enable the Phpro_Sales module.

Testing the web API

To validate the web API you can send a POST request with curl or a REST client such as Postman
Make sure the token is in the Autorization header and the relationNumber is in the body of the request.

# With JSON:
curl -X POST "http://test.phpro.local:8080/rest/V1/order/1/relationnumber" -H "Content-Type:application/json" -H "Authorization: Bearer 45462b4c237b57e4gxbfke10epwm87w9" -d '{"relationNumber":"CRM-123456789"}'
# With XML:
curl -X POST "http://test.phpro.local:8080/rest/V1/order/1/relationnumber" -H "Content-Type:application/xml" -H "Authorization: Bearer dude8aigg6u5ix1swqhdia80k83bxo60" -d '<xml><relationNumber>CRM-123456789</relationNumber></xml>'

A successful request returns a HTTP 200 status and a response body "true". Also the relation number should be saved or updated in the sales_order table (if the order exists). 

 

To conclude, it's very straightforward to set up a basic REST or SOAP API with Magento 2 to integrate with external systems, for example: CRM or ERP.  

 

Resources

http://devdocs.magento.com/guides/v2.0/get-started/bk-get-started-api.html

http://www.slideshare.net/AndraElenaLungu/api-in-magento-2

http://devdocs.magento.com/guides/v2.0//extension-dev-guide/service-contracts/service-to-web-service.html

-- blogpost by Stef Liekens

Share