Magento шаг за шагом: REST API

от автора

В предыдущей статье мы рассмотрели создание «скелета» для экспорта заказов. В этой же рассмотриим создание такого же скелета, но импорта через 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> 

ссылка на оригинал статьи http://habrahabr.ru/post/232603/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *