В предыдущей статье мы рассмотрели создание «скелета» для экспорта заказов. В этой же рассмотриим создание такого же скелета, но импорта через REST API.
С Вики: REST (сокр. от англ. Representational State Transfer — «передача репрезентативного состояния») — в более употребительном узком смысле под REST понимается метод взаимодействия компонентов распределённого приложения в сети Интернет, при котором вызов удаленной процедуры представляет собой обычный HTTP-запрос (обычно GET или POST; такой запрос называют REST-запрос), а необходимые данные передаются в качестве параметров запроса. Этот способ является альтернативой более сложным методам, таким как SOAP, CORBA и RPC.
По стандартному htaccess Magento, все запросы поданные на /api/ должны отправляться на api.php:
RewriteRule ^api/([a-z][0-9a-z_]+)/?$ api.php?type=$1 [QSA,L]
Почему нужно стоить использовать именно стандартный API? Помимо того, что для него уже все готово, API-запросы всегда запускаются в режиме администратора (Mage::app()->getStore()->isAdmin() === true), что подразумевает как языковую независимость для EAV-аттрибутов, так и отсутствие каких-либо событий из области frontend.
За конфигурацию REST отвечает конфигурационный файл api2.xml в директории etc модуля, нода api2.
Итак, попробуем расширить предыдущий функционал еще и импортом.
Допустим, формат вводных данных с внешней системы будет таков:
<orders>
<order>
<id>145000003</id>
<shipment>
<tracking>uiwq12889124</tracking>
<items>
<item>
<sku>21</sku>
<qty>1</qty>
</item>
</items>
</shipment>
</order>
<order>
<id>145000003ZZZ</id>
<shipment>
<tracking>uiwq128zzz89124</tracking>
<items>
<item>
<sku>21</sku>
<qty>1</qty>
</item>
</items>
</shipment>
</order>
</orders>
В указанном запросе один заказ будет реальным, второй — нет.
app/code/local/Easy/Interfacing/etc/api2.xml
<?xml version="1.0"?>
<config>
<api2>
<resource_groups>
<easy_interfacing translate="title" module="api2">
<title>Easy Interfacing REST</title>
<sort_order>30</sort_order>
<children>
<easy_interfacing_orders translate="title" module="api2">
<title>Orders</title>
<sort_order>50</sort_order>
</easy_interfacing_orders>
</children>
</easy_interfacing>
</resource_groups>
<resources>
<easy_interfacing_orders translate="title" module="api2">
<group>easy_interfacing</group>
<model>easy_interfacing/api2_order</model>
<filter>easy_interfacing/api2_order_filter</filter>
<title>Orders</title>
<sort_order>10</sort_order>
<versions>1</versions>
<routes>
<route_collection>
<route>/easy_interfacing/order</route>
<action_type>collection</action_type>
</route_collection>
</routes>
<privileges>
<guest>
<update>1</update>
</guest>
</privileges>
<attributes translate="id shipment" module="easy_interfacing">
<id>Order ID</id>
<shipment>Shipment data</shipment>
</attributes>
</easy_interfacing_orders>
</resources>
</api2>
</config>
Нода resource_groups отвечает за ACL в System > Web Services > REST Roles. В самих же ресурсах REST API мы указываем группу принадлежности easy_interfacing и модель api2_order, которая будет отвечать за функционал.
Путь к интерфейсу укаазн в route_collection, мы также указываем, что обработка будет вестись на множественном количестве элементов (action_type = route_collection). В нашем REST API мы все будем делать на гостевом доступе, чтоб не мучаться с паролями на этапе обучения: в коде разница будет только в названии класса.
Нода attributes отвечает за ACL фильтр импортируемых данных (Mage_Api2_Model_Acl_Filter). Так же можно форсировать аттрибуты в массив импортируемых данных добавлением списка в ноду forced_attributes. Все возможные варианты можно найти в api2.xml любого модуля, позволяющего делать импорт/экспорт данных, например, Mage_Sales или Mage_Catalog.
По умолчанию все аттрибуты не должны быть массивами, что не подходит под наш формат (shipment — массив), поэтому создадим собственный фильтр, где исправим эту проблему в методе Mage_Api2_Model_Acl_Filter::collectionIn:
app/code/local/Easy/Interfacing/Model/Api2/Order/Filter.php
class Easy_Interfacing_Model_Api2_Order_Filter extends Mage_Api2_Model_Acl_Filter
{
public function collectionIn($items)
{
$nodeName = key($items);
if (!is_numeric(key($items[$nodeName]))) {
$items[$nodeName] = array($items[$nodeName]);
}
if (is_array($items[$nodeName])) {
foreach ($items[$nodeName] as &$item) {
$item = $this->in($item);
}
}
return $items[$nodeName];
}
}
Не забудьте проставить ACL доступы в System > Web services > REST — Roles:
и в System > Web services > REST — Attributes:
И, наконец создадим сам класс API:
app/code/local/Easy/Interfacing/Model/Api2/Order.php
class Easy_Interfacing_Model_Api2_Order extends Mage_Api2_Model_Resource
{
const RESULT_ERROR_NOT_FOUND = 404;
const RESULT_ERROR_IMPORT = 500;
const RESULT_SUCCESS = 200;
protected $_responseItems = array();
protected function _addResult(array $item, $errorCode, $errorMessage)
{
$result = array('result' => $errorCode, 'id' => $item['id']);
if ($errorMessage) {
$result['error'] = $errorMessage;
}
$this->_responseItems[] = $result;
}
}
В нем нам особо ничего не нужно, только _addResult и пара констант кодов ошибок. Так же создадим REST-класс, унаследованный от этого:
app/code/local/Easy/Interfacing/Model/Api2/Order/Rest.php
class Easy_Interfacing_Model_Api2_Order_Rest extends Easy_Interfacing_Model_Api2_Order
{
public function dispatch()
{
$this->_filter = Mage::getModel('easy_interfacing/api2_order_filter', $this);
parent::dispatch();
$this->_render($this->_responseItems);
}
protected function _multiUpdate(array $filteredData)
{
foreach ($filteredData as $item) {
$order = Mage::getModel('sales/order')->loadByIncrementId($item['id']);
/* @var $order Mage_Sales_Model_Order */
if (!$order->getId()) {
$this->_addResult($item, self::RESULT_ERROR_NOT_FOUND);
continue;
}
try {
Mage::getSingleton('easy_interfacing/order')->import($order, $item);
$this->_addResult($item, self::RESULT_SUCCESS);
} catch (Exception $ex) {
$order->addStatusHistoryComment('Failed importing order: ' . $ex->getMessage())->save();
$this->_addResult($item, self::RESULT_ERROR_IMPORT, $ex->getMessage());
}
}
}
}
В нем переопределим метод dispatch, чтобы подменить фильтр на наш и сменить немного рендеринг, так как по умолчанию Magento выдаст немного кривой ответ, основанный на коллекции сообщений из getResponse().
Так как мы указывали action_type=collection, мы реализуем метод _multiUpdate. В $filteredData всегда будет находиться уже отфильтрованный массив (если из ACL аттрибутов убрать ID, то Easy_Interfacing_Model_Order::import бросит исключение, или даже крэшнется).
app/code/local/Easy/Interfacing/Model/Api2/Order/Rest/Guest/V1.php
class Easy_Interfacing_Model_Api2_Order_Rest_Guest_V1 extends Easy_Interfacing_Model_Api2_Order_Rest
{
}
Guest-класс API так же нужно создать, т.к. именно он будет вызван при гостевом доступе к REST.
В конечном итоге, если Вы все сделали правильно, при запросе на http:///api/rest/easy_interfacing/order/ методом PUT через любой REST-клиент вводных данных указанных выше, придет ответ вида:
<?xml version="1.0" ?>
<magento_api>
<data_item>
<result>500</result>
<id>145000003</id>
<error>Not implemented</error>
</data_item>
<data_item>
<result>404</result>
<id>14501100003</id>
</data_item>
</magento_api>
Автор: vlx