Magento 2.1: Добавление категорий и товаров по API
Недавно хотел сделать один из проектов интернет-магазина, используя CMS Magento 2. Одна из задач проекта была возможность загрузки товаров и категорий из XML поставщика на сайт по API. Эта задача была реализована. И т.к. в интернете возникает множество вопросов, каким образом загружать товары в Magento 2 по API, делюсь своими наработками в этой статье.
Взаимодействие с API Magento
Для взаимодействия с API Magento используется 3 метода:
- Get — получить данные
- Post — отправить данные
- Put — изменить данные
Для удобство работы, предлагаю создать такие функции, что бы при каждой операции к ним обращаться. Код функций у меня получился такой:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
function Get ($requestURL, $setHaders) { //Отправляет GET запрос на нужный адрес и получает ответ if( $curl = curl_init() ) { //echo '1'.PHP_EOL; curl_setopt($curl, CURLOPT_HTTPHEADER, $setHaders); curl_setopt($curl, CURLOPT_URL, $requestURL); curl_setopt($curl, CURLOPT_RETURNTRANSFER,true); $out = curl_exec($curl); //print_r ($out); curl_close($curl); } return $out; }; function Post ($requestURL, $JsonData, $setHaders) { //Процедура отправки данных на сервер $ch = curl_init(); curl_setopt($ch,CURLOPT_URL, $requestURL); curl_setopt($ch,CURLOPT_POSTFIELDS, $JsonData); curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST"); curl_setopt($ch, CURLOPT_HTTPHEADER, $setHaders); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $Status = curl_exec($ch); if($Status === false){ echo "Curl error: " . curl_error($ch)."\n"; }else{ $response = $Status ?: ""; } curl_close($ch); return $response; } function Put ($requestURL, $JsonData, $setHaders) { //Процедура отправки данных на сервер $ch = curl_init(); curl_setopt($ch,CURLOPT_URL, $requestURL); curl_setopt($ch,CURLOPT_POSTFIELDS, $JsonData); curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "PUT"); curl_setopt($ch, CURLOPT_HTTPHEADER, $setHaders); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $Status = curl_exec($ch); if($Status === false){ echo "Curl error: " . curl_error($ch)."\n"; }else{ $response = $Status ?: ""; } curl_close($ch); return $response; } |
При этом, переменныая $setHaders — служат для доступа к API. У меня это глобальные переменные и выглядят следующим образом:
1 2 |
$adminToken = 'mx3evqctwoi3b0qy2xjtqv4sgbk2qh29'; $setHaders = array('Content-Type:application/json','Authorization:Bearer '.$adminToken); |
Переменная $requestURL — формируется при составлении запроса (т.к. зависит от операции, которую необходимо выполнить).
Переменная $JsonData — это массив данных, который будет передаваться в API.
Добавление категорий товаров Magento 2 по API
Для добавления категории в Magento 2 нужно сформировать массив данных категории и отправить его на сайт.
Массив данных формируется следующим образом:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
$CategoryData = array ( 'parent_id' => $parent_id, 'name' => $name, 'is_active' => true, //"position" => 10, 'include_in_menu' => true, // ), 'custom_attributes' => array ( 'CategoryXmlId' => $XmlId, 'is_new' => false, 'is_sale' => false, 'meta_description' => $meta ['meta_description'], 'meta_keywords' => $meta ['meta_keywords'], 'meta_title' => $meta ['meta_title'], 'description' => $meta ['description'], // "display_mode"=> "PRODUCTS", // "is_anchor"=> "1", // "custom_use_parent_settings"=> "0", // "custom_apply_to_products"=> "0", // "url_key"=> $tname.'_'.$XmlId // if not set magento uses the name to generate it // "url_path"=> $tname.'_'.$XmlId, //Добавляем ID из XML поставщика, что бы потом по нему искать категорию // "automatic_sorting"=> "0", // 'my_own_attribute' => 'value1234', // <-- your attribute // 'label' => 'sdfsdfdsfsdf', )); |
Далее декодируем массив в json и передаем в функцию Post таким образом:
1 2 3 4 5 |
$JsonData = json_encode(array ('category' => $CategoryData)); $requestURL = $requestIP."/rest/V1/categories"; require_once 'post.php'; //Процедура отправки данных на сервер $response = Post($requestURL, $JsonData, $setHaders); |
Тут $requestIP — так же глобальная переменная (что бы при изменении адреса сайта, не менять его в нескольких местах кода). Формируется таким образом:
1 2 |
$Domain = 'www.XXX.ru'; $requestIP = 'http://'.$Domain; |
При этом, API Magento должно вернуть json, с данными добавленной категории. Этот ответ можно декодировать функцией:
1 |
json_decode($response, true) |
И, если необходимо, записать в лог или вывести в консоль, для отладки.
Обновление категорий товаров
Обновление категорий товаров происходит аналогично. Только в данном случае, мы пользуемся методом Put, а не Post.
Функция обновления товара по API будет выглядеть следующим образом:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
function UpdateCategory ($IDCategory, $parent_id, $name, $XmlId, $meta) { global $requestIP; global $setHaders; $CategoryData = array ( //'id' => $IDCategory, //'parent_id' => $parent_id, //'name' => $name, 'is_active' => true, //"position" => 10, 'include_in_menu' => true, // ), 'custom_attributes' => array ( 'CategoryXmlId' => $XmlId, 'is_new' => false, 'is_sale' => false, 'meta_description' => $meta ['meta_description'], 'meta_keywords' => $meta ['meta_keywords'], 'meta_title' => $meta ['meta_title'], 'description' => $meta ['description'], )); $JsonData = json_encode(array ('category' => $CategoryData)); $requestURL = $requestIP."/rest/V1/categories/".$IDCategory; require_once 'put.php'; //Процедура отправки данных на сервер $response = Put($requestURL, $JsonData, $setHaders); return true; } |
Добавление товаров в каталог Magento 2 по API
Категории в каталог добавлять достаточно просто. Товары — немного сложнее. В товарах существуют изображения, которые так же добавляются в массив добавления товара. Так же, товарам нужно присвоить необходимые атрибуты. Если в группе атрибутов такого нет — создать его. Но.. давайте по порядку.
Первым делом, формируем массив данных о товаре. Функция выглядит так:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
function productDataNew ($sku, $name, $price, $PricePurchase, $weight, $quantity, $is_in_stock, $category_ids, $description, $short_description, $NewsToDate, $country, $vendor, $vendor_code, $barcode, $stock_status, $demission, $gabarit, $volume, $nettoweight, $materials, $garantee, $ImagesUrl, $meta, $url_key, $color1, $feature1, $featured) { global $attribute_set_id; $ImageArr = ImageXML($ImagesUrl, $name); if (!$ImageArr) { echo 'Товар не добавлен, т.к. не удалось скачать ни одной картинки'.PHP_EOL; return FALSE;} if ($weight == "") {$weight = 0;} return $productData = array( 'sku' => $sku, 'name' => $name, 'visibility' => 4, //'catalog', Видно и в каталоге и в поиске 'type_id' => 'simple', //Единичный, т.е. товар без вариаций 'price' => $PricePurchase, //Бена БЕЗ скидки. Цена обновляется при обновлении товара 'status' => 1, 'attribute_set_id' => $attribute_set_id, 'weight' => $weight, 'extension_attributes' => array ( 'stock_item' => array ( //'item_id' => 1, //'stock_id' => 1, 'qty' => $quantity, 'is_in_stock' => $is_in_stock, 'is_qty_decimal'=> false, ) ), 'custom_attributes' => array( array( 'attribute_code' => 'url_key', 'value' => $url_key ), array( 'attribute_code' => 'special_price', 'value' => $price ), //Цена СО скидкой (цена из XML) array( 'attribute_code' => 'category_ids', 'value' => $category_ids ), //Категория обновляется при обновлении товара array( 'attribute_code' => 'description', 'value' => $description ), //Не обновляется, при обновлении товара array( 'attribute_code' => 'short_description', 'value' => $short_description ), //array( 'attribute_code' => 'sale', 'value' => $sale ), //array( 'attribute_code' => 'new', 'value' => $new ), array( 'attribute_code' => 'news_from_date', 'value' => date("Y-m-d H:i:s") ), array( 'attribute_code' => 'news_to_date', 'value' => $NewsToDate), //Атрибуты товара не обновляются, при обновлении товара array( 'attribute_code' => 'country1', 'value' => array(strval($country)) ), //array( 'attribute_code' => 'available', 'value' => $available ), array( 'attribute_code' => 'vendor1', 'value' => array(strval($vendor)) ), array( 'attribute_code' => 'vendor_code', 'value' => $vendor_code ), array( 'attribute_code' => 'barcode', 'value' => $barcode ), array( 'attribute_code' => 'stock_status1', 'value' => array(strval($stock_status)) ), array( 'attribute_code' => 'demission', 'value' => $demission ), array( 'attribute_code' => 'gabarit', 'value' => $gabarit ), array( 'attribute_code' => 'volume', 'value' => $volume ), array( 'attribute_code' => 'nettoweight', 'value' => $nettoweight ), array( 'attribute_code' => 'materials1', 'value' => array(strval($materials)) ), array( 'attribute_code' => 'garantee', 'value' => $garantee ), array( 'attribute_code' => 'meta_title', 'value' => $meta['title'] ), array( 'attribute_code' => 'meta_keyword', 'value' => $meta['keywords'] ), array( 'attribute_code' => 'meta_description', 'value' => $meta['description'] ), array( 'attribute_code' => 'feature1', 'value' => $feature1 ), array( 'attribute_code' => 'featured', 'value' => $featured ), ), 'media_gallery_entries' => $ImageArr, ); |
Как видно, в функции используется переменная $ImageArr. Это массив с изображениями товара. Формируется он следующим образом:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
foreach ($ImagesUrl as $value){ if ( ServerResponseCode ($value) ) { //Eсли URL доступен (отдает код ответа 200) $ImgMas = explode(".", $value); $TypeImage = end($ImgMas); if ($TypeImage == 'jpg') {$TypeImage = 'jpeg';} if ($TypeImage == 'jpg') {$TypeImage = 'jpeg';} if ($TypeImage == 'png') {$TypeImage = 'png';} if ($i == 1) {$types = array ('image', "small_image", "thumbnail");} else {$types = array ('image');} //echo (translit($name)); $XML[$i] = array( 'mediaType' => 'image', 'label' => $name, 'position'=> $i, 'types' => $types, 'disabled'=> false, 'content' => array( 'base64_encoded_data' => base64_encode(file_get_contents($value)), 'type' => 'image/'.$TypeImage, //Если jpg нужно подставлять jpeg, если png - png 'name' => translit($name), )//) ); //print_r ($TypeImage); //print_r ($XML); $i++; } else {echo 'Изображение не добавлено, т.к. отсутствует на сервере '.$value.PHP_EOL;} } if (isset($XML)) { $sum = array(); foreach( $XML as $key => $arr ){ $sum[$key] = $arr; } } else {$sum = FALSE;}; //print_r ($sum); return array_reverse($sum); } |
В самом начале функции проверяется, доступен ли файл изображения на сервере, по коду ответа.
Функция ServerResponseCode у меня выглядит так:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
function ServerResponseCode ($url) { //Проверяет существование URL. global $sleep, $attempts; $i = 1; a: if( $curl = curl_init() ) { curl_setopt($curl,CURLOPT_URL,$url); curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 1); curl_setopt($curl,CURLOPT_RETURNTRANSFER,true); curl_setopt($curl,CURLOPT_NOBODY,true); curl_setopt($curl,CURLOPT_HEADER,true); $out = curl_exec($curl); //echo $out; curl_close($curl); } if ( strripos($out, '200') ) { return true; } else { sleep ($sleep); if ($i >= $attempts) { return false; } else { $i++; goto a; } } } |
Но этого делать не обязательно. Просто в ходе работы было замечено, что картинки, указанные в XML поставщика иногда отсутствуют на сервере. Что бы это не вызывало ошибки скрипта — проверяем и пропускаем такую картинку.
При формировании массива, для Magento нужно указывать тип изображения (jpg, jpeg, png), поэтому в функцию встроена проверка типа изображения.
Сому картинку нужно передавать в запросе, в формате MIME base64. Поэтому используется функция base64_encode для кодирования в данный формат.
Таким образом формируется массив картинок, который после добавляется в массив данных о товаре.
Функцию обновления данных о товаре тут приводить не буду. Но она абсолютно аналогична. В массив данных собираются параметры, которые нужно обновить. Делается по аналогии с обновлением данных категории.
Далее отправляем сформированный массив данных о товаре на сервер:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
$JsonData = json_encode(array('product' => $productData)); //print_r ($productData); $requestURL = $requestIP."/rest/V1/products"; require_once 'post.php'; //Процедура отправки данных на сервер $response = Post($requestURL, $JsonData, $setHaders); $StatudProdAdd = json_decode( $response, true ); //print_r ($StatudProdAdd); if (isset($StatudProdAdd['message'])) { echo 'Тован НЕ добавлен. Сообщение: '.$StatudProdAdd['message'].PHP_EOL; } else {echo 'Тован добавлен. ID: '.$StatudProdAdd['id'].PHP_EOL;} echo 'Конец добавления товара '.$sku.' '.date("H:i:s").PHP_EOL.PHP_EOL; |
Добавление атрибутов товаров по API Magento 2
В массиве данных о товарах так же присутствуют пользовательские атрибуты, вида:
1 |
array( 'attribute_code' => 'vendor_code', 'value' => $vendor_code ), |
Во-первых, добавляются в массив они не по значению, а по ID в списке значений атрибута (у меня большинство атрибутов имеют тип — список значений).
Для этого, нам понадобиться функция, которая будет запрашивать массив всех доступных значений атрибута товара. Передаем в нее код атрибута. Выглядит она так:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
function GetAllAttributes ($attributeCode, $UpdateAttr) { global $requestIP; global $setHaders; global $AllAttributesGlob; global $DoUpdateAllAttributesGlob; require_once 'get.php'; if (!$DoUpdateAllAttributesGlob and file_exists('AllAttributesGlob.php')){ include 'AllAttributesGlob.php'; $AllAttributesGlob = $_array; } if ($UpdateAttr OR !isset($AllAttributesGlob[$attributeCode])) { echo 'Массив атрибутов не существует или задан параметр обновления массива. Заново собираем массив атрибутов.'.PHP_EOL; $requestURL = $requestIP.'/rest/V1/products/attributes/'.$attributeCode;//.'/options.'; $Json = Get($requestURL, $setHaders); $AllAttributesGlob[$attributeCode] = array(); $AllAttributesGlob[$attributeCode] = json_decode($Json, true); //print_r ($AllAttributesGlob[$attributeCode]); } else {echo 'Массив атрибутов существует и не задан параметр обновления массива. Пользуемся прежним массивом атрибутов.'.PHP_EOL;} require_once 'array_to_file.php'; arraytofile ($AllAttributesGlob, 'AllAttributesGlob.php'); return $AllAttributesGlob[$attributeCode]; } |
Специально не стал ее очищать от своих «доработок». В принципе, функция пишется в 3 строчки. Но у меня, если вы заметили, массив атрибутов сохраняется в файл. Потом, при необходимости читается из файла или обновляется массив, если в функцию передан специальный параметр, или массив не существует.
Это сделано уже позже, для того, что бы ускорить работу скрипта. Т.к. если при каждом добавлении атрибута товара считывать все существующие значения аатрибутов, это занимает значительное время в функции добавления товара. Вы, можете пойти тем же путем. Или изобрести свой «велосипед», если тут важна производительность.
Если нам нужно получить ID значения атрибута по значению, просто ищем по массиву, примерно так:
1 2 3 4 5 6 7 8 9 10 11 12 |
function GetIDValueProductAttribete ($JsonArr, $attributeValue) { //Получает id значения атрибута //print_r ($JsonArr); foreach ($JsonArr as $value) { if ($value['label'] == $attributeValue) { //print_r ($value['value']); return $value['value']; } } } |
И напоследок — самое интересное, функция добавления значения атрибута. Она проверяет, существует ли такой атрибут и, если не существует, добавляет его:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
function AddValueProductAttribete ($attributeCode, $attributeValue) { //Функция проверяет, есть ли указанное значение данного атрибута //Если нет - создает. В любом случае, возвращает ID значения атрибута. //Исключение - если в значении атрибута - пустая строка. Тогда возвращает false global $setHaders, $requestIP; if ($attributeValue <> '') { $attributeValue = str_replace('™','', $attributeValue); if (mb_strlen($attributeValue, 'UTF-8') > 3) { //mb_strlen //echo 'Длина строки значения атрибута '.mb_strlen($attributeValue, 'UTF-8').'. Преобразовываем формат.'.PHP_EOL; //$attributeValue = mb_strtolower($attributeValue,'UTF-8'); $attributeValue = mb_ucfirst($attributeValue); } //else {$attributeValue = mb_strtoupper ($attributeValue);} //echo $attributeValue.' '; require_once 'get.php'; //Запрос текущих значений трибута $JsonArr = GetAllAttributes ($attributeCode, false); //print_r ($JsonArr['options']); //$AttributeID = $JsonArr[attribute_id]; //Проверяем, существует ли уже данное значение атрибута foreach ($JsonArr['options'] as $key => $value) { if ($value['label'] == $attributeValue) { $IDProductAttr = $value['value']; break; }; } if ( isset($IDProductAttr) ) { echo 'Значение атрибута '.$attributeCode.' существует: '.$attributeValue.PHP_EOL; return $IDProductAttr; //Если значение существует - возвращаем его id } else { //Если значение не существует - добавляем echo 'Значение атрибута '.$attributeCode.' не существует. Добавляем: '.$attributeValue.PHP_EOL; //print_r ($JsonArr['options']); $AttributeData = array( 'label' => $attributeValue, ); $JsonData = json_encode(array('option' => $AttributeData)); require_once 'post.php'; $requestURL = $requestIP.'/rest/V1/products/attributes/'.$attributeCode.'/options'; $Json = Post($requestURL, $JsonData, $setHaders); $JsonArr = json_decode($JsonData, true); //print_r ($JsonArr); $JsonArr = GetAllAttributes ($attributeCode, true); //И обновляем глобальный массив атрибутов + получаем ID атрибута return GetIDValueProductAttribete($JsonArr['options'], $attributeValue); } } else {return false;} } |
Удачи в освоении Magento!
Поделиться "Magento 2.1: Добавление категорий и товаров по API"