Есть такой способ самообучения — как выполнение тестовых заданий. Его преимущество в том что объём задания конечен, сроки ограничены. Это не позволяет тянуть резину до бесконечности или самозабвенно вырисовывать завихрения и завитушки архитектурных изысков.

Приятный бонус такой тренировки скиллов заключается в знакомстве с новыми технологиями и бизнес процессами, которое кроме всего прочего востребованы в реальных задачах.

На этот раз надо было сделать страничку для формирования заказа покупателя в сервисе «Мой склад». Для меня это как полёт на Луну: в веб разработке я чуть меньше чем новичок, с фронтэндом знаком только по наслышке, а тут целую страницу надо разработать, ох ты Йожик!
Любая критика и советы приветствуются.

В коментах очень много ругательств, моё решение настолько ужасно, что для него сделали рефакторинг во что то приличное:
michael_vostrikov
От нечего делать сделал небольшой рефакторинг этого задания (хотя там много чего еще можно поменять), не столько для вас, сколько для тех, кто потом найдет в поиске эту статью:
коммиты, разметка, отправка формы.

Поехали!


Первым делом конечно гуглить, нагуглилась только ссылка на документацию, туториалов, примеров — ноль.

Ещё нагуглилось: «JSON API доступен для подписчиков на всех тарифах, кроме Бесплатного» уупс! Платного мне конечно ни кто не дал, покупать не камильфо, но я подумал что если дали такое задание, то наверное на Бесплатном что то там функционирует и продолжил работу.

И конечно нагуглилось «moysklad-client — npm — JavaScript клиент для комфортной работы с API сервиса МойСклад», но я с JS исключительно на «Вы», и по условиям задания, написать надо на PHP. Так что даже разбираться не стал, что там на JS можно делать.

Первое


Первое что надо сделать, это познакомиться с документацией. Познакомился.
Второе — составить план. Составил.
План, начало.
Действие первое — авторизация.
Действие второе — показать список Номенклатур.
Действие третье — добавить Заказ покупателя.
Действие четвёртое — добавить Позиции в Заказ покупателя.
Цель достигнута, конец плана.

Авторизация


Я видел код в котором для общения с API использовался cUrl. Я не знаю что такое cUrl, я не знаю как принято общаться с API, но если есть код который можно скопипастить, то проверить его пригодность не сложно. Скопировал вставил, обработал напильником — получилось.

Не буду утомлять вас интимными подробностями о дружбе напильника с копипастой, вот работающий код:

function setupCurl($apiSettings)
{
    $curl = curl_init();
    curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false);
    curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
    curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($curl, CURLOPT_FOLLOWLOCATION, false);

    $userName = $apiSettings[MOYSKLAD_USERNAME];
    $userPassword = $apiSettings[MOYSKLAD_PASSWORD];
    curl_setopt($curl, CURLOPT_USERPWD, "$userName:$userPassword");
    curl_setopt($curl, CURLOPT_USERAGENT, $apiSettings[MOYSKLAD_USER_AGENT]);
    return $curl;
}

Параметры curl:

  • RETURNTRANSFER — не только отправляем запрос, но и записываем ответ;
  • USERPWD — реквизиты аутентификации;

Остальные опции не знаю зачем нужны, тупо копипаста.
Не факт, что заголовки авторизации надо отправлять в каждом запросе, хотя не факт, что они не сбросились после первой же отправки… кто подскажет?

Итак, это была инициализация объекта curl для обмена сообщениями с сервером API.

Использование:

function curlExec($curlObject)
{

    $response = curl_exec($curlObject);

    $curlErrorNumber = curl_errno($curlObject);
    if ($curlErrorNumber) {
        throw new Exception(curl_error($curlObject));
    }

    return $response;
}


Чистая копипаста, не спрашивайте меня почему так.

Показать список Номенклатур


Одних номенклатур оказалось мало, для Заказа покупателя, надо указать юридическое лицо Поставщика и контрагента Покупателя. У владельца учётки «Мой склад» может быть несколько юридических лиц, контрагентов — ясно понятно 100500, но конкретный Заказ покупателя, это заказ конкретного Контрагента в адрес конкретного Юридического лица.

Поэтому с номенклатурами обождём, займёмся сторонами «договора» — сделки.

Юридические лица

$curl = setCurl(
    $curl,
    $apiSettings[MOYSKLAD_API_URL] . $apiSettings[MOYSKLAD_GET_JURIDICAL_PERSON],
    $apiSettings[MOYSKLAD_GET_JURIDICAL_PERSON_METHOD]);

$persons = getJuridicalPerson($curl);

function setCurl(&$curlObject, $uri, $method)
{
    curl_setopt($curlObject, CURLOPT_URL, $uri);

    curl_setopt($curlObject, CURLOPT_HTTPGET, true);
    switch ($method) {
        case MOYSKLAD_METHOD_GET:
            break;
        case MOYSKLAD_METHOD_POST:
            curl_setopt($curlObject, CURLOPT_POST, true);
            break;
        case MOYSKLAD_METHOD_PUT:
            curl_setopt($curlObject, CURLOPT_PUT, true);
            break;
    }

    return $curlObject;
}

function getJuridicalPerson($curlObject)
{
    $response = curlExec($curlObject);
    $data = json_decode($response, true);
    $result = $data['rows'];
    return $result;
}

Извиняюсь за ужасные названия констант, но мне с такими спокойней, точно ни с чем не перепутаю. Да я знаю что у case (switch) есть ветка default, но мне спокойней вбетонировать в код значение по умолчанию и не надеяться на превратности судьбы с case.

У каждой команды API свой адрес и свой метод, setCurl — устанавливает адрес и метод.

Для получения списка юридических лиц устанавливаем соответствующий адрес и метод ( адрес и метод задаются в настройках, настройки подгружаются методом function getSettings(){ $apiConfig = include('moysklad_curl_details.php'); return $apiConfig;} ).

После этого методом getJuridicalPerson исполняем curl, получаем ответ в JSON, из ответа забираем только массив 'rows'. Получили, сохранили, отложили.

С Контрагентами поступаем аналогично: setCurl => getCounterparty, Номенклатуры по тому же алгоритму: setCurl => getNomenclature.

Если бы это было не тестовое на два вечера после работы, а на два дня безработного специалиста, то можно было бы это автоматизировать, но это было тестовое в стиле — «лишь бы работало», поэтому я не стал изгаляться.

Для меня цель тестового была в том что бы пригубить и попробовать на вкус JSON API, рисовать красоту — цели не было.

Данные получили — это вообще не вопрос, дело дурацкое — дело не хитрое, интересней было как то это вывести на страничку, а потом со странички забрать, вот это была задачка.

Фронтэнд


Не знаю как правильно, я сделал так:

echo '<form action="#" onsubmit="return false;" id="orderForm"  ><p>Доступные юридические лица:<br />';
foreach ($persons as $key => $person) {
    $personId = $person['id'];
    echo '<label for="' . $personId . '">' . $person['name'] . '</label><input type="radio" data-organization-type="1" id="' . $personId . '" name="organization"><br />';
}
echo 'Доступные контрагенты:<br />';
foreach ($counterparty as $key => $person) {
    $personId = $person['id'];
    echo '<label for="' . $personId . '">' . $person['name'] . '</label><input type="radio" data-counterparty-type="1" id="' . $personId . '" name="counterparty"><br />';
}
echo 'Номенклатура товаров:<br />';
foreach ($nomenclature as $key => $position) {
    $positionId = $position['id'];
    echo '<label for="' . $positionId . '">' . $position['name'] . ', количество для заказа => </label><input type="text" id="' . $positionId . '" data-position-type="1"><br />';
}
echo '
<input type="submit" name="Сформировать заказ покупателя" onclick="sendOrder();"><br /></p></form>'

Общий алгоритм:

  1. пишем название раздела
  2. пишем название позиции,
  3. пишем тег input, в атрибут id пишем идентификатор полученный из API,
  4. пишем соответствующий атрибут data-organization-type / data-counterparty-type / data-position-type, поскольку для получения атрибута надо присвоить значение, то присваиваем

(вообще конечно можно использовать атрибуты без значений, но я не разобрался как).

По клику на кнопку «Сформировать заказ покупателя», форма не отправляется — «return false;», но вызывается функция — «sendOrder();».

Отправить заказ


echo '
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js" type="text/javascript"></script>
<script type="text/javascript">
function sendOrder(){
    var $text_field = $('#orderForm :input:text');
    
    var position = {};    
    $text_field.each(function() {        
        var this_val = $(this).val();    
        var may_assign = this_val>0 || this_val !="";
        var is_it_position = $(this).data('position-type');    
        
        if ( may_assign && is_it_position > 0){
            position[this.id] = this_val;
        }
    });
    
    var $radio_field = $('#orderForm :input:radio:checked');

    var counterparty = {};
    var organization = {};
    $radio_field.each(function() {
        var this_val = $(this).val();
        
        var is_it_counterparty = $(this).data('counterparty-type');
        var is_it_organization = $(this).data('organization-type');
        
        var may_assign = this_val>0 || this_val !="";
        
        if ( may_assign && is_it_counterparty > 0){
            counterparty[this.id] = this_val;
        }
        if ( may_assign && is_it_organization > 0){
            organization[this.id] = this_val;
        }    
    });

C JS мне кажется всё более чем прозрачно:

  1. $('#orderForm :input:text'); — выбрали все теги input с типом text внутри тега с идентификатором orderForm
  2. $text_field.each — для каждого элемента выполняем анонимную функцию
  3. var this_val = $(this).val(); — сохранили значение
  4. var may_assign = this_val>0 || this_val !=""; — вычислили что значение не пустое
  5. var is_it_position = $(this).data('position-type'); — вычислили что атрибут data-position-type установлен
  6. if ( may_assign && is_it_position > 0){ position[this.id] = this_val; } — если значение не пустое и этот input соответствует позиции, то добавляем в массив позиций соответствующий элемент, идентификатор в качестве индекса гарантирует уникальность.

Проделываем такую же акробатику с юридическими лицами и контр агентами, с тем отличием что для анализа выбираем все input с типом «radio» в состоянии «checked»:

$('#orderForm :input:radio:checked'), и кроме того значение элемента input нам не требуется, нам просто надо знать кого ( одного ) из всего списка выбрал наш Покупатель.

Теперь когда данные для отправки в обработку готовы, надо сформировать запрос:

    var postData = JSON.stringify({position : position, counterparty : counterparty , organization : organization});
    console.log(postData);
    $.ajax({
        type: "POST",
        url: "moyskald_add_order.php",        
        data: postData,
        contentType: "application/json; charset=utf-8",
        dataType: "text",
        timeout: 10000,        
        error: function(){
            alert("сбой добавления заказа");        
        },
        success: function(data){alert(data);},
        failure: function(errMsg) {
            alert(errMsg);
        }        
    });

К этой копипасте мне добавить не чего, метод отправки — «POST», обработка будет выполнена — «moyskald_add_order.php», данные уходят в формате -«application/json; charset=utf-8», приходят в формате — «text».

Данные вообще не нужны, хотя конечно хороший тон это сообщить пользователю «Ваш заказ принят» или «Сбой добавления заказа», и ладно.

Едем дальше, следующий пункт прибытия — «Обработка».

Обработка


С обработкой вышла осечка. Я ламер-эникейщик и для разработки использую XAMPP (под Win10), который как то раз настроил и забыл. И вот что то там такое настроено, что я GET запросы в PHP-скрипт получаю как нормальный человек, а POST-запросы, только через одно место.

Но GET — не камильфо, потому что GET кэшуруется, а у нас не факт что ответ сервера будет точно таким как в прошлый раз, особенно когда ты ведёшь разработку, и серверная логика меняется каждые пять минут.

Поэтому пришлось смириться с использованием чёрной магии в виде file_get_contents(«php://input»), потому что, что бы я ни делал, но var_export($POST) стабильно выдавал «array()». Между прочим, буду благодарен за серию пинков в верном направлении.

А дальше всё просто:

$data = json_decode($rawData, true);

$rawPosition = $data['position'];
$rawCounterparty = $data['counterparty'];
$rawOrganization = $data['organization'];

Разобрали входные данные.

Дёрнули Юридическое лицо и контрагента:

const FIRST_INDEX = 0;
$counterpartyId = $counterpartyIdCollection[FIRST_INDEX];
$organizationId = $organizationIdCollection[FIRST_INDEX];

Сформировали поля запроса:

$textAddCustomerOrder = '
{
  "name": "' . time() . '",
  "organization": {
    "meta": {
      "href": "https://online.moysklad.ru/api/remap/1.1/entity/organization/' . $organizationId . '",
      "type": "organization",
      "mediaType": "application/json"
    }
  },
  "agent": {
    "meta": {
      "href": "https://online.moysklad.ru/api/remap/1.1/entity/counterparty/' . $counterpartyId . '",
      "type": "counterparty",
      "mediaType": "application/json"
    }
  }
}
';

$apiSettings = getSettings();
$curl = setupCurl($apiSettings);

$curl = setCurl(
    $curl,
    $apiSettings[MOYSKLAD_API_URL] . $apiSettings[MOYSKLAD_ADD_CUSTOMER_ORDER],
    $apiSettings[MOYSKLAD_ADD_CUSTOMER_ORDER_METHOD]);

curl_setopt($curl, CURLOPT_POSTFIELDS, $textAddCustomerOrder);
curl_setopt($curl, CURLOPT_HTTPHEADER, array(
        'Content-Type: application/json',
        'Content-Length: ' . strlen($textAddCustomerOrder))
);

Тут получилось схалявить и зафигачить JSON без json_encode, тупо вклеить нужные идентификатору, в нужные места. Обязательно делаем POSTFIELDS — $textAddCustomerOrder, HTTPHEADER — 'Content-Length: '. strlen($textAddCustomerOrder).

отправляем запрос на обработку на сервер API: $customerOrderId = setCustomerOrder($curl), в ответе забираем 'id'.

Заказ добавлен.

Добавить Позиции в Заказ покупателя


С этим пунктом Плана, ни каких проблем, кроме необходимости использования json_encode для форматирования текста запроса и floatval для количества товара в позиции. Без floatval сервер API выдаёт ошибку формата для поля «quantity» (и «reserve» соответственно).

$isPositionArray = is_array($rawPosition);

$orderPositions= array();
if ($isPositionArray) {
    foreach ($rawPosition as $id => $quantity) {

        $positionQuantity=floatval($quantity);

        $orderPositions[] =
            [
                "quantity" =>$positionQuantity,
                "price"=>0,
                "discount"=>0,
                "vat"=>0,
                "assortment" =>[
                    "meta"=>[
                        "href"=>"https://online.moysklad.ru/api/remap/1.1/entity/product/$id",
                        "type"=>"product",
                        "mediaType"=>"application/json"
                    ]
                ],
                "reserve"=>$positionQuantity,
            ];
    }
}

Ремарка: foreach ($rawPosition as $id => $quantity), в ключах массива записаны идентификаторы позиций, в значениях элементов массива — количество для заказа.

Мой фэншуй требует создания каждого массива по отдельности и добавления всех «элементарных» массивов в общую кучу, но… мне кажется это будет излишний фанатизм.

Не уверен что в заказе покупателя надо выставлять резерв, но мне кажется это больше бизнес требование, чем механика работы с API.

С ценой смешно вышло, в задании требовалось вывести список номенклатур, для меня это просто перечень наименований и соответствующих ТТХ, в которые цена не входит, но если подумать, то цена это самое первое ТТХ в подобном приложении, но у меня в форме заказа цены нет, поэтому — проехали.

С отправкой API запроса теперь мне кажется всё предельно ясно:

$jsonResponse = 'empty';
$isContainPosition = count($orderPositions)>0;
if($isContainPosition ){
    $jsonOrderPositions= json_encode($orderPositions);

    $curl = setupCurl($apiSettings);

    $curl = setCurl(
        $curl,
        $apiSettings[MOYSKLAD_API_URL]
        . $apiSettings[MOYSKLAD_ADD_ORDER_POSITION_PREFIX]
        . $customerOrderId
        . $apiSettings[MOYSKLAD_ADD_ORDER_POSITION_SUFFIX],
        $apiSettings[MOYSKLAD_ADD_ORDER_POSITION_METHOD]);

    curl_setopt($curl, CURLOPT_POSTFIELDS, $jsonOrderPositions);
    curl_setopt($curl, CURLOPT_HTTPHEADER, array(
            'Content-Type: application/json',
            'Content-Length: ' . strlen($jsonOrderPositions))
    );

    $jsonResponse = setCustomerOrderPosition($curl);
}

Адрес-команда запроса «вычисляется» несколько странным образом:
$apiSettings[MOYSKLAD_API_URL]
        . $apiSettings[MOYSKLAD_ADD_ORDER_POSITION_PREFIX]
        . $customerOrderId
        . $apiSettings[MOYSKLAD_ADD_ORDER_POSITION_SUFFIX]

По документации должно быть :"/entity/customerorder/{id}/positions".

Типа даже нативная PHP строка, бери да прямо так и пиши — {id} подставиться само, но я не могу допустить хардкода в отношении записи команды API, нет конечно за время написания тестового команда не поменяется, но феншуй требует такие глобальные вещи выносить в константы, аминь. Хотя когда я смотрю на исходники всяких open source фреймворков, я вижу что хардкод там сплошь и рядом, но это делают они, а это делаю я, и я так не делаю.

Эпилог


Вот собственно и всё. API, на Бесплатном тарифе, обрабатывает запросы в последнюю очередь, поэтому иногда таймаута в 10 секунд не достаточно. В остальном работает стабильно. Я проверял.
Но наша цель конечно была не в этом, в результате выполнения задания, мы научились работать с JSON Web API и научились мутить фронтэнд с выводом информации в форму и передачей пользовательского ввода для обработки в серверный скрипт.
Ура :)

PostScriptum


Перед написанием статьи я чуть плотнее погуглил на тему «php json api мой склад» и нашёл массу реализаций, но не для JSON, а для XML. Из десятка найденных, парочка похожа на реально рабочие, но это не актуально потому что XML API грозятся отключить с 31 марта 2017.

А может всё будет так же как с «JSON API доступен для подписчиков на всех тарифах, кроме Бесплатного».

Конструктивная критика и ссылки на best practics ПРИВЕТСТВУЮТСЯ! мне не стыдно быть колхозником, но не хорошо бы если лучше?

Ссылки


  1. Git
Поделиться с друзьями
-->

Комментарии (45)


  1. SuperDoker
    23.01.2017 08:38
    +2

    Смешивать HTML и PHP не очень хорошая идея, советую разобраться в вопросе и сделать то же самое только правильно


    1. SbWereWolf
      23.01.2017 10:28

      можно пример правильной «ручной» реализации? без использования фреймворка?


      1. ilyaplot
        23.01.2017 14:49

        function render($template, $data)
        {
        extract($data);
        ob_start();
        require $template;
        return ob_get_clean();
        }


        Шаблон пишется на чистом PHP.


        1. SbWereWolf
          23.01.2017 15:19
          +1

          спасибо.


    1. SbWereWolf
      23.01.2017 23:20

      мои статьи по SQL набирали 6к/9к/5к просмотров, у этой статьи всего 1.7к, не очень то люди и заметили, а кто заметил, тот влепил минус — как это принято ни кто не смотрел исходники выложенные на гите, и приняли код приведённый в статье за чистую монету, отсюда эти… не обоснованные обвинения в «лапше», в проекте вообще 4 файла:

      1. moysklad_routine_library — планировалась библиотека функций, по факту вышло что можно было обойтись всего тремя, хотя они общие для клиентской и серверной части, так что всё равно надо было выносить в отдельный файл.
      2. moysklad_customer_order — клиентская часть — смесь модели и фронтэнда, мне кажется для одностраничного сайта вполне допустимо, при чём файл явно делитьсян а две части — сначала PHP с подготовкой данных, затем html с выводом — «смешение» php c html чисто формальное.
      3. moyskald_add_order — серверная часть.
      4. moysklad_curl_details — файл с параметрами API и аутентификации.

      на мой вкус мухи отдельно, котлеты отдельно.

      но у людей голова ломается от отсутствия классов, от отсутствия автозагрузчика, видимо когда то им за это хорошенько досталось и теперь они комплексуют, хорошо хоть не обвинили в отказе от фремворка.

      Единственный человек по делу написал это ilyaplot — ещё раз спасибо.
      Очень жаль что ни кто не прокомментировал проблему с пустым POSTом но «полным» — php://input

      такое резюме по отклику на статью.
      ЗЫ
      нет что бы поржать вместе со мной, зачем то обосрали, видимо потребность в «срать» имеет приоритет над «ржать» :)


      1. michael_vostrikov
        24.01.2017 10:31

        мои статьи по SQL набирали 6к/9к/5к просмотров, у этой статьи всего 1.7к, не очень то люди и заметили, а кто заметил, тот влепил минус

        Судя по рейтингу и комментам, ваши статьи по SQL тоже мало чего полезного содержат. Раз у этой статьи минусов больше, то может и полезного в ней меньше?


        как это принято ни кто не смотрел исходники выложенные на гите, и приняли код приведённый в статье за чистую монету

        Код в статье ничем не отличается от исходников на github. И код этот не очень хороший, и по стилю, и по архитектуре, и по логике работы.


        планировалась библиотека функций, по факту вышло что можно было обойтись всего тремя, хотя они общие для клиентской и серверной части, так что всё равно надо было выносить в отдельный файл.

        Воот. А потом в рабочем проекте добавятся еще 100500 глобальных функций, и окажется, что неплохо было бы их еще разделить — работа с curl, с массивами, с валютой и т.д. Так может сразу использовать классы и пространства имен?


        «смешение» php c html чисто формальное

        Оно очень даже тесное, так как у вас нет HTML-разметки, она выводится в строках PHP-кодом, с соответствующими слэшами возле кавычек.


        но у людей голова ломается от отсутствия классов, от отсутствия автозагрузчика, видимо когда то им за это хорошенько досталось

        Почему вы так уверены, что люди не писали без классов и автозагрузчика? Как раз писали, и знают, к чему это может привести.


        Очень жаль что ни кто не прокомментировал проблему с пустым POSTом но «полным» — php://input

        Извините, вам здесь разве обязаны помогать? Ваш код, сами разбирайтесь, вы же программист. Тем более что это ошибка из-за вашего незнания основ. Об этом ниже.


        нет что бы поржать вместе со мной

        Ни в вашей статье, ни в ваших комментах с негативным отношением к окружающим нет ничего смешного.


      1. michael_vostrikov
        24.01.2017 11:05

        По коду. И нет, отмазка "это же тестовое задание, тут надо просто чтоб работало" не прокатит. Его дают не за этим, а как раз наоборот — чтобы проверить, как вы пишете код.


        — Неправильно расставлены скобки, пробелы и отступы. PHPStorm, о котором вы говорили ниже, вам видимо не очень помогает. Имена переменных и фукнций не соответствуют их цели — например, $сounterparty это массив, а название в единственном числе.


        echo 'Доступные контрагенты:<br />';
        Зачем вы пишете разметку в строках? Вам лень тег php закрыть?)


        '<label for="' . $personId . '">' . $person['name'] . '</label>'
        Потенциальная XSS-уязвимость. Что если кто-нибудь заведет контрагента с названием "<script>[jscode]</script>"?


        $counterpartyId = $counterpartyIdCollection[FIRST_INDEX];
        Зачем вы отправляете массив, если вам нужен только первый элемент? Также зачем-то проверяются data-organization-type и data-counterparty-type, хотя они везде установлены в "1".


        contentType: 'application/json; charset=utf-8'
        Вот и ваша проблема с POST. PHP не принимает данные в формате JSON (документация). Надо убрать contentType, jQuery сам подставит правильный, данные отправлять через $('#orderForm').serialize(), и поменять обработку на сервере, потому что данные сериализуются немного в другом формате.


        — Работа функции getNomenclature() не соответствует названию. Ее результат полностью зависит от того, что мы вызывали до нее, так что может вернуть и nomenclature и order и все что угодно. Надо перенести установку URL в нее, и в остальных функциях аналогично. А можно и в класс объединить, тогда снаружи вместо этого


        $curl = setCurl(
            $curl,
            $apiSettings[MOYSKLAD_API_URL] . $apiSettings[MOYSKLAD_GET_JURIDICAL_PERSON],
            $apiSettings[MOYSKLAD_GET_JURIDICAL_PERSON_METHOD]);
        $persons = getJuridicalPerson($curl);
        
        $curl = setCurl(
            $curl,
            $apiSettings[MOYSKLAD_API_URL] . $apiSettings[MOYSKLAD_GET_COUNTERPARTY],
            MOYSKLAD_GET_COUNTERPARTY_METHOD);
        $counterparty = getCounterparty($curl);
        
        $curl = setCurl(
            $curl,
            $apiSettings[MOYSKLAD_API_URL] . $apiSettings[MOYSKLAD_GET_NOMENCLATURE],
            MOYSKLAD_GET_NOMENCLATURE_METHOD);
        $nomenclature = getNomenclature($curl);

        будет вот так


        $apiClient = MoySkladApiClient::create();
        
        $personList = $apiClient->getJuridicalPersonList();
        $counterpartyList = $apiClient->getCounterpartyList();
        $productList = $apiClient->getProductList();

        Все просто и понятно.


        От нечего делать сделал небольшой рефакторинг этого задания (хотя там много чего еще можно поменять), не столько для вас, сколько для тех, кто потом найдет в поиске эту статью:
        коммиты, разметка, отправка формы.


        1. SbWereWolf
          25.01.2017 00:00

          $сounterparty это массив, а название в единственном числе
          — я устал от множественных чисел, приписок array collection list и т.п., больше не практикую.
          Зачем вы пишете разметку в строках? Вам лень тег php закрыть?)
          видимо
          Потенциальная XSS-уязвимость. Что если кто-нибудь заведет контрагента с названием ""?
          контрагентов заводит владелец аккаунта, хочет себе в ногу выстрелить — сколько угодно, хотя я думаю разработчики API такую возможность уже исключили
          Зачем вы отправляете массив, если вам нужен только первый элемент?

          если всё можно обработать по одной технологии то зачем придумывать две? я ни когда не ставлю перед собой цели оптимизации с выгадыванием миллисекунд
          Вот и ваша проблема с POST. PHP не принимает данные в формате JSON (документация). Надо убрать contentType, jQuery сам подставит правильный, данные отправлять через $('#orderForm').serialize(), и поменять обработку на сервере, потому что данные сериализуются немного в другом формате.

          спасибо огромное, за это вам можно все наезды простить :)
          Работа функции getNomenclature() не соответствует названию

          соответствует, предполагалось, что вернётся чёрти что и это надо будет как то парсить, но во всех трёх случаях и с организацией и с контрагентами и с номенклатурой всё оказалось намного проще, можно было конечно из API_шного ответа выдирать только айдишник и имя, пихать их в массив и его возвращать, но для меня этот проект был почеркушкой а не фундаментом для дальнейшей разработки и ваш серьёзный подход это просто последующие итерации которые происходят, тогда когда они нужны, в данном случаи они были не нужны.
          От нечего делать сделал небольшой рефакторинг этого задания

          спасибо, ваш разбор полётов был одной из целей этой публикации


          1. michael_vostrikov
            25.01.2017 06:35

            я устал от множественных чисел, приписок array collection list и т.п., больше не практикую.

            Вам сложно будет пройти тестовое задание с таким подходом. Никому неохота разбираться в особеностях названий чужого кода.

            контрагентов заводит владелец аккаунта, хочет себе в ногу выстрелить — сколько угодно, хотя я думаю разработчики API такую возможность уже исключили

            Разработчикам API такая уязвимость не грозит, они просто данные отправляют.

            если всё можно обработать по одной технологии то зачем придумывать две? я ни когда не ставлю перед собой цели оптимизации с выгадыванием миллисекунд

            А оптимизация здесь ни при чем, это банально лишний код.

            // было
            var $radio_field = $('#orderForm :input:radio:checked');
            $radio_field.each(function() {
                var this_val = $(this).val();
            
                var is_it_counterparty = $(this).data('counterparty-type');
                var is_it_organization = $(this).data('organization-type');
            
                var may_assign = (this_val > 0 || this_val != '');
            
                if (may_assign && is_it_counterparty > 0) {
                    counterparty[this.id] = this_val;
                }
                if (may_assign && is_it_organization > 0) {
                    organization[this.id] = this_val;
                }
            });
            
            // стало
            var organization = $('#orderForm :input:radio:checked[name=organization]').attr('id');
            var counterparty = $('#orderForm :input:radio:checked[name=counterparty]').attr('id');
            

            (полная версия)

            соответствует, предполагалось, что вернётся чёрти что и это надо будет как то парсить

            Вы не поняли. getNomenclature() просто выполняет запрос с текущими настройками curl (и кстати ничем не отличается от getCounterparty(), у них абсолютно одинаковый код). Если я перед ее вызовом установлю другой URL, то вернутся другие данные, с номенклатурой не связанные.


            1. SbWereWolf
              25.01.2017 09:17

              и кстати ничем не отличается от getCounterparty()

              отличается, в контрагентах из полученного массива сохраняются только 'rows', в номенклатурах такой обработки нет, она идёт снаружи метода.
              var organization = $('#orderForm :input:radio:checked[name=organization]').attr('id');

              мне бы ваши познания в jquery :)
              Вам сложно будет пройти тестовое задание с таким подходом. Никому неохота разбираться в особеностях названий чужого кода

              у этого тестового была цель выполнить в оговоренный срок, а не качественно написать, у меня отведённое время ушло на «борьбу» с PHP по обработке $_POST, от которой пришлось отказаться и на работу с jquery, на то что бы причесать код времени не осталось

              задание принимал бизнес-аналитик / проджект менеджер который собирает команду фрилансеров, ему в срок важнее, качества кода он не оценит.


              1. michael_vostrikov
                25.01.2017 10:05

                отличается
                Да ну?) moysklad_routine_library.php
                function getCounterparty($curlObject)
                {
                    $response = curlExec($curlObject);
                    $data = json_decode($response, true);
                    $result = $data['rows'];
                    return $result;
                }
                
                function getNomenclature($curlObject)
                {
                    $response = curlExec($curlObject);
                    $data = json_decode($response, true);
                    $result = $data['rows'];
                    return $result;
                }
                

                И суть не в этом, а в том, что они не делают то, что отражено в названии. Если я просто вызову getCounterparty(), я не получу никакой сounterparty.

                на то что бы причесать код времени не осталось

                Для статьи код можно было причесать, при ее написании вас никто не торопил.

                ему в срок важнее, качества кода он не оценит

                Качество оценит команда фрилансеров, которые будут с вами работать. И читатели статьи.

                мне бы ваши познания в jquery :)

                Тут достаточно уметь гуглить, «jquery select by name» и «jquery get id attribute». Хотя не вижу принципиальных отличий от ваших селекторов.

                Если вы не знаете PHP и jQuery, зачем тогда писать обучающие статьи.


                1. SbWereWolf
                  25.01.2017 13:14

                  Для статьи код можно было причесать, при ее написании вас никто не торопил

                  мои обстоятельства меня торопили, на статью ушло около 4-х часов, это пол воскресения, а ещё надо был она работе выправить ножки на материнке, в подъезде организовать установку замка на чёрный ход в подъезд и вообще могу я в воскресение отдаться безделью?
                  Тут достаточно уметь гуглить, «jquery select by name» и «jquery get id attribute». Хотя не вижу принципиальных отличий от ваших селекторов.

                  принципиальное отличие межу мои и вашим вариантами только в опыте автора решения в использовании jquery
                  Если вы не знаете PHP и jQuery, зачем тогда писать обучающие статьи

                  «на пять не знает даже Бог», если что. Код решает задачу, и я в статье объясняю технологию решения, чем не обучение, учу плохому? вы внесли свою лепту что бы статья стала учить хорошему :)


                  1. michael_vostrikov
                    25.01.2017 13:41

                    вообще могу я в воскресение отдаться безделью

                    Можно было вообще статью не писать, было бы больше свободного времени) И вы думаете, у других авторов нет своих дел? Тем не менее, они уделяют внимание коду, который будут читать другие.


                    принципиальное отличие межу мои и вашим вариантами только в опыте автора решения в использовании jquery

                    jQuery и опыт тут ни при чем. Если бы вы хотели получить одно значение, а не массив, вы бы поискали информацию и нашли решение. Тем более что это первые ссылки в гугле.


                    Код решает задачу, и я в статье объясняю технологию решения

                    Вот как раз не объясняете, а говорите "я не знаю" и "я не разобрался".


                    учу плохому? вы внесли свою лепту

                    То, что кто-то решил исправить ошибки, не означает, что надо писать плохие статьи. Особенно они вредны для новичков, которые не всегда могут разобраться, что там правильно, а что нет.


                    1. SbWereWolf
                      26.01.2017 01:34

                      Тем более что это первые ссылки в гугле.

                      у Гугла поисковая выдача заточена под пользователя, у когото это первая десятка записей, у кого то не первая, я по jQuery раз в пол года ищу, у меня всякая фигня в первой десятке.
                      Вот как раз не объясняете, а говорите «я не знаю» и «я не разобрался».

                      статья про работу с API, как работать с API я разобрался.
                      То, что кто-то решил исправить ошибки, не означает, что надо писать плохие статьи. Особенно они вредны для новичков, которые не всегда могут разобраться, что там правильно, а что нет.

                      Пишите хорошие, а то приходиться писать плохие — свято место пусто не бывает :))


                      1. oxidmod
                        26.01.2017 01:52

                        скрин первой страницы выдачи?)
                        jquery select by name
                        jquery get id attribute


                        я вообще очень едко гуглю чтото по jquery и 1-2 линка на https://api.jquery.com


                      1. michael_vostrikov
                        26.01.2017 07:34

                        Ваши отмазки выглядят глупо. Думаю, программист смог бы отличить фигню от нефигни, да и вообще найти ответ на такой простой вопрос. Значит вы и не пытались искать. И да, проверил через VPN и анонимайзер в приватном окне браузера и в Internet Explorer, которым не пользуюсь. И в google и в yandex и даже в bing есть нужные ссылки.


                        Про работу с API написано в документации к API. Зачем делать статью с точно такой же информацией? Может ценность как раз и должна быть в коде для работы с ним?


                        Отсутствие хороших статей по API Мой Склад не означает, что надо обязательно писать плохие. А если решили написать, то тогда не возмущайтесь на минусы.


                        1. SbWereWolf
                          26.01.2017 07:49

                          :))


  1. L0NGMAN
    23.01.2017 08:39

    После «Я не знаю что такое cUrl» я перестал читать…


  1. MetaDone
    23.01.2017 08:41

    самое первое — https://getcomposer.org/download/
    после — смотрите что такое psr-2 и psr-4
    после — https://github.com/guzzle/guzzle
    слишком много замечаний выйдет, нет смысла перечислять все


    1. MetaDone
      23.01.2017 08:45

      и логин с паролем можно вынести в конфиг


      1. SbWereWolf
        23.01.2017 10:28

        логин и пароль в конфиге, конфиг называется moysklad_curl_details.php.
        зачем мне psr-4 в проекте на две функции? накидано всё в файл moysklad_routine_library.php, зачем огород городить с psr-4?
        С psr-2 есть расхождения?

        https://github.com/guzzle/guzzle — посмотрим, спасибо.


        1. MetaDone
          23.01.2017 10:59
          +2

          странный конфиг.

          зачем мне psr-4 в проекте на две функции?

          чтобы хоть как-то структурировать код. сейчас там лапша, для одноразового скрипта, который выполнили и забыли — пойдет, но статья говорит про самообучение, потому надо привыкать делать все по устоявшимся в сообществе правилам.


          1. SbWereWolf
            23.01.2017 12:52
            -1

            хоть как то код структурирован функциями, можно конечно вообще всё сделать функциями, а потом придумать классы и вообще неймспейсы как минимум один для логики приложения, другой неймспейс для общения с API, но это не про тестовое задание, это уже про «самозабвенно вырисовывать завихрения и завитушки архитектурных изысков» — этого мне на основной работе хватает.

            про psr1/2/3 как бы нету замечаний? как бы я обычно в рамках общепринятых стандартов «творю».


            1. MetaDone
              23.01.2017 13:36

              про psr-2 — используйте http://cs.sensiolabs.org/
              psr-3 это про логгирование, что-то у вас я там ничего такого не увидел
              тут не завихрения, а просто будет не лапша и возможность отделить мух от котлет
              это как раз «про тестовое задание» — потому что в таком виде мало кто посчитает что оно выполнено.


              1. SbWereWolf
                23.01.2017 14:25

                да логирования нет, psr-1/psr-2 обеспечиваются phpStorm / Code / Reformat не так ли? или «cs.sensiolabs.org» способен на большее?


  1. oxidmod
    23.01.2017 12:31

    1. SbWereWolf
      23.01.2017 12:45

      мне показалось что это XML вариант?


      1. oxidmod
        23.01.2017 13:34

        мне кажется, что сменить xml на json не столь проблемно. Но тут есть неплохой костяк для разработки нормального API-клиента на php


        1. SbWereWolf
          23.01.2017 14:27

          не стояло задачи разработать API, но если бы что то уже было кем то разработано, то конечно было бы глупо не использовать, а взять за основу xml вариант и переделать его в json… надо сначала в коде разобраться, потом брать, мне кажется по времени такой подход не укладывается в два вечера после рабочего дня.


          1. oxidmod
            23.01.2017 14:33

            может немного дольше, но гораздо качественней. Тем более что код там вполне вменяемый и не требует лсишком много усилий чтобы его понять.


  1. michael_vostrikov
    23.01.2017 15:56

    Статья с пометкой "tutorial" и с кучей всяких "я не знаю" и "я не разобрался"? Вы серьезно?


    1. SbWereWolf
      23.01.2017 16:17

      вы считаете пометка how_to подойдёт больше?


      1. michael_vostrikov
        23.01.2017 16:45

        Я считаю, что статьи такого уровня вообще не стоит писать. Тем более в качестве руководства для других.


        1. SbWereWolf
          23.01.2017 17:42

          я бы подобные статьи с удовольствием читал если бы их кто то их писал, особенно я был бы рад им в поисковой выдаче Google, это лучше чем изобретать очередной велосипед, но мало кто такие элементарные вещи разжёвывает, потому что всем и так всё ясно.


          1. michael_vostrikov
            24.01.2017 10:09

            Вот как раз поэтому такие статьи и не нужны. Вот другой найдет эту статью, подумает "Это же Хабр, тут наверно правильные и качественные решения", накопирует ваш код и получит проблемы, технические или организационные в виде отказа в приеме на работу. Если уж писать, то писать надо правильный код с понятными объяснениями, а не "я не знаю, я не разобрался". Тогда это будет хороший обучающий материал.


            Это не велосипед, а тестовое задание. Документация на API есть, учебников по PHP хватает. Получается, вы хотели бы не думать головой при выполнении тестового задания, а просто скопипастить готовое решение? Может цель задания и была в том, чтобы проверить, как вы сами организуете код для работы с таким API.


            1. SbWereWolf
              24.01.2017 10:34

              в работе не надо разрабатывать то что кто то уже разработал, не надо тратить время на то что бы разобраться с чужим кодом, если этот код работает без нареканий, надо тратить время на решение задач а не на проверку «инструментов», если что то работает, то надо этим пользоваться, а не разбирать по винтикам, что бы удостовериться что все винтики живые, ни один не разболтался и резьба ни где не сорвана. Разработка это умение комбинировать инженерные решения, а не умение потратить рабочее время на изобретение велосипеда.


              1. michael_vostrikov
                24.01.2017 11:12

                Ну вот у вас и была задача — сделать клиента для API, к которому готовых клиентов нет. Как вы будете на работе делать взаимодействие с API, которое в соседнем отделе разрабатывается? Попросите их специально для вашего проекта клиента написать? А вдруг вы сами в этом отделе будете работать, как вы клиента напишете?


                1. SbWereWolf
                  25.01.2017 00:13

                  ну вот я её и решил не тратя время на изучение тех вещей которые уже работают, в этом задании у меня был затык с фронт-эндом, плюс PHP за каким то чёртом заменяло пробелы на нижнее подчёркивание, вот это был ад, аж пришлось запариться на input:radio.


                  1. michael_vostrikov
                    25.01.2017 06:36

                    Я говорил не об этом, а о качестве кода и обучающего материала, которые появились в процессе ее решения.


  1. ilyaplot
    23.01.2017 16:24

    Я совершенно не разделяю ваши методы разработки. Вот из-за таких, как вы, приходится обрабатывать невалидный YML через регулярки, а потом парсить.

    схалявить и зафигачить JSON без json_encode, тупо вклеить

    Мы же на хабре, тут такого не должно быть.


    1. SbWereWolf
      23.01.2017 17:39

      схалявить и зафигачить JSON без json_encode, тупо вклеить

      если следовать принципу Keep It Simple Stupid — тупо текст из документации к API это проще чем генерация этого текста.
      Хотя генерация конечно методологически надёжней, но документация API после публикации не поменяется.
      принцип You ain't gonna need it говорит о выполнении необходимого минимума действий для достижения цели. Если модификация не предполагается, то «статичный» текст полностью удовлетворяет «условиям задачи»


      1. ellrion
        23.01.2017 18:34

        О да, оправдывать не качественный код принципами KISS и YAGNI это круто


  1. ellrion
    23.01.2017 18:36

    Код очень стилистически соответствует самому изложению. Эдакое размашистое разгильдяйство. Исключительно по моим ощущениям конечно.


    1. SbWereWolf
      23.01.2017 18:45

      главное код тестами покрыть :) а когда нет тестов, то живые пользователи тоже сгодятся, но в этом случае важно успеть накатить бэкап раньше чем пользователю прочухают в чём дело: )


  1. babylon
    23.01.2017 21:57

    Развлекательно-отвлекательная статья. То, что автор хотя бы думает в направлении JSON API уже замечательно. Раз есть комментарии, то автор замечен по любому. На хабре много самоучек и самоделок. Это не плохо и не хорошо. Это состояние индустрии. Заглянул давеча на сайт, посвященный известной игровой платформе. Но люди почему то неутомимо и упорно пишут свой самокат без педалей. Видимо всё таки зависит от возраста и от опыта. Я еще раз вспомнил о Jquery. Это ширпотреб в хорошем смысле, чего пока нельзя сказать о JSON API и Angular. Я — за ширпотреб. Можно с тестами можно без них. Тесты не спасают от неправильной парадигмы мышления и выбора средств.