This blog post is part of an exploratory series in building a simple module. My official goal – build a module to make it easier to integrate the Amazon Echo (Alexa) with a Magento store; my unofficial goal – explore different areas of Magento 2. Nothing like doing a real project to make sure the approach makes sense.
So in this blog post I am going to summarize creating a new “area” and “front controller” to receive Alexa JSON POST requests (without using the standard Magento REST/SOAP support).
Magento REST/SOAP Support
I am going to walk through creating a new “area”, but first I wanted to describe why I am not using the built in Magento 2 REST/SOAP support in this post.
Magento 2 includes support for exposing service contracts via REST/SOAP. These are designed to make a Magento internal API easily exposed as a web API. The functionality however has not (yet) been designed to support other people’s definitions of API contracts. For example, JSON encoded responses in Magento 2 today always use lowercase with underscores (“snake_case”) whereas the Alexa API requires capitalization (“camelCase”). The default REST+JSON bindings onto Magento 2 service contacts cannot be used with Alexa because it cannot return camel cased JSON responses. Support may be added in the future, but is not a current priority.
So the Alexa module that I will be creating in this series will act as the bridge between the JSON based Alexa web service protocol and Magento. A PHP interface (like a service contract) will be defined, but the default Magento 2 REST/SOAP encoding rules will not be used.
In Magento 2, there are a number of defined “areas” that are shipped with the product. Each area has a “front name”, which is basically the first segment of the URL. You may be familiar with the area “adminhtml” which has a front name of “admin”, meaning any URL starting with “/admin” dispatched to the “adminhtml” area.
To find this binding, look in vendor/magento/module-backend/etc/di.xml for the following markup. In particular look for “adminhtml” (the area name) and “admin” (the front name).
<type name="Magento\Framework\App\AreaList"> <arguments> <argument name="areas" xsi:type="array"> <item name="adminhtml" xsi:type="array"> <item name="frontNameResolver" xsi:type="string">Magento\Backend\App\Area\FrontNameResolver</item> <item name="router" xsi:type="string">admin</item> </item> </argument> </arguments> </type>
Note that the area name (not the front name) is used as the directory name to hold additional area specific configuration settings. For example, there can be an etc/adminhtml/di.xml file holding additional dependency injection configuration only loaded when URLs match the adminhtml area.
The Magento 2 REST and SOAP build in support have their own areas as well. These can be found in vendor/magento/module-webapi/etc/di.xml. The “webapi_rest” area is bound to the “/rest” front name and the “webapi_soap” area is bound to “/soap”.
<type name="Magento\Framework\App\AreaList"> <arguments> <argument name="areas" xsi:type="array"> <item name="webapi_rest" xsi:type="array"> <item name="frontName" xsi:type="string">rest</item> </item> <item name="webapi_soap" xsi:type="array"> <item name="frontName" xsi:type="string">soap</item> </item> </argument> </arguments> </type>
There is also the special default area called “frontend” (representing the store front) which all URLs that don’t match one of the specified area names are sent to. This means the customer store front experience has nice short URLs, as long as you make sure your store front URLs do not collide with an existing area name.
The dependency injection configuration for the store front can be found in vendor/magento/module-store/etc/di.xml. (Note the argument “default” is set to “frontend”.)
<type name="Magento\Framework\App\AreaList"> <arguments> <argument name="areas" xsi:type="array"> <item name="frontend" xsi:type="array"> <item name="frontName" xsi:type="null" /> <item name="router" xsi:type="string">standard</item> </item> </argument> <argument name="default" xsi:type="string">frontend</argument> </arguments> </type>
In this blog we are going to declare a new area called “alexa” with a front name of “alexa” so that all URLs starting with “/alexa” are sent to this new area for processing. Here is a complete sample di.xml file to achieve this, in app/code/AlanKent/Alexa/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"> <type name="Magento\Framework\App\AreaList"> <arguments> <argument name="areas" xsi:type="array"> <item name="alexa" xsi:type="array"> <item name="frontName" xsi:type="string">alexa</item> </item> </argument> </arguments> </type> </config>
Note: there are negatives of declaring new areas. For example, all of the dependency injection settings for the “frontend” area are not loaded for the “alexa” area. This might result in duplication of di.xml settings in different files. (You can have configuration shared across all areas, but it is not easy to share markup between two areas. You need to copy the markup instead.) In this example, the ease of use to parse the URL and invoke special purpose logic to fully process the HTTP request an form the HTTP response was deemed worthwhile.
Once URLs are routed to the appropriate area, they are processed by a “front controller”. This allows different areas to process HTTP requests differently. For example, store front URLs (and administration pages) are mapped onto action controllers (not described here, but these are the most common form of controllers in Magento 2); REST URLs (and SOAP endpoints) are parsed to identify the appropriate service contract to invoke; and there are several other areas also used in Magento 2. Thus the job of a front controller is to parse the rest of the URL after the front name using rules specific to the identified area.
The front controller to use is defined by the di.xml file for the area. For example, the following is a snippet from the file app/code/AlanKent/Alexa/etc/alexa/di.xml. This is in the “alexa” area subdirectory, which is only loaded when the URL is identified as a match for the “/alexa” front name. It defines that the AlanKent\Alexa\App\FrontController class should be used whenever the Magento\Framework\App\FrontControllerInterface is requested. This is how the alexa area front controller is registered with the Magento framework.
<?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="Magento\Framework\App\FrontControllerInterface" type="AlanKent\Alexa\App\FrontController" /> ... </config>
Front Controller Basics
The “alexa” area front controller is a PHP class that implements the interface Magento\Framework\App\FrontControllerInterface. The dispatch() function is called with a “request” data structure providing access to the HTTP request. It must return a “response” data structure which holds the HTTP response. The Alexa module currently uses two result types – raw (plain text) and JSON. The following is an example of forming JSON content to return.
$result = $this->resultFactory->create(ResultFactory::TYPE_JSON); $result->setHttpResponseCode(200); $result->setHeader('Content-Type', 'application/json', true); $result->setData($jsonArray); return $result;
The following is an example of forming plain text response.
$result = $this->resultFactory->create(ResultFactory::TYPE_RAW); $result->setHttpResponseCode(...); $result->setHeader('Content-Type', 'text/plain', true); $result->setContents($message); return $result;
This was just a quick note to show an alternative to using the built in REST/SOAP support for exposing service contracts. In general it is better to use the REST/SOAP bindings where possible. In the case of the Alexa protocol, it is defined by Amazon in a format not directly supported by the REST/SOAP bindings support. As such it was easier to define a new area and implement the JSON parsing directly in PHP code.
The Alexa project is still an active work in progress, and can be found at https://github.com/alankent/magento2-alexa. There may still be some significant changes coming to this module as the code settles down.
A future post will be on the Vagrantfile provided in this project for Windows based Magento 2 development.