Обработка ошибки 152 «Недостаточно баллов»

PHP версии 5, формат JSON, используется функция file_get_contents

Баллы API Директа версии 5 начисляются всем рекламодателям один раз в 60 минут, но для каждого рекламодателя используется собственное расписание начисления баллов без привязки к началу астрономического часа. Пример демонстрирует логику повторного выполнения запросов, если первый запрос завершается ошибкой с кодом 152.

Для демонстрации используется функция sleep, которая позволяет устанавливать задержки выполнения скрипта. Задержки задаются в массиве set_DELAY в секундах, так чтобы с момента первого запуска было выполнено не более 5 повторных запросов с увеличивающимися интервалами, при этом были покрыты ближайшие 60 минут с момента первого получения ошибки.

При разработке приложения мы рекомендуем использовать более надежные механизмы повторного выполнения запросов. Вы можете организовать логику выполнения запросов с использованием планировщика задач (например, cron) или системы очередей (например, Gearman).

<?php
//--- Настройки ---------------------------------------------------------//
$set_DEBUG = true; // Вывод отладочной информации (html): false - не выводить; true - выводить
$set_DELAY = array(0, 360, 540, 720, 900, 1080); // Массив задержек выполнения повторных запросов (в секундах)
$err_NOT_ENOUGH_UNITS = 152; // Ошибка "Недостаточно баллов"
ini_set('max_execution_time', 5400); // Максимальное время выполнения скрипта - 5400 секунд (90 минут)
// Настройки для вывода содержимого буфера, которые позволяют делать вывод на экран при использовании функции sleep
ob_implicit_flush();

//--- Входные данные запроса --------------------------------------------//
// Адрес сервиса Campaigns для отправки JSON-запросов (регистрозависимый)
$url = 'https://api.direct.yandex.com/json/v5/campaigns';
// OAuth-токен пользователя, от имени которого будут выполняться запросы
$token = 'ТОКЕН';
// Логин клиента рекламного агентства
// Обязательный параметр, если запросы выполняются от имени рекламного агентства
$clientLogin = 'ЛОГИН_КЛИЕНТА';
      
//--- Подготовка запроса ------------------------------------------------//
// Установка HTTP-заголовков запроса
$headers = array(
   "Authorization: Bearer $token",                    // OAuth-токен. Использование слова Bearer обязательно
   "Client-Login: $clientLogin",                      // Логин клиента рекламного агентства
   "Accept-Language: ru",                             // Язык ответных сообщений
   "Content-Type: application/json; charset=utf-8"    // Тип данных и кодировка запроса
);
         
// Параметры запроса к серверу API Директа
$params = array(
   'method' => 'get',                                 // Используемый метод сервиса Campaigns
   'params' => array(
      'SelectionCriteria' => (object) array(),        // Критерий отбора кампаний. Для получения всех кампаний должен быть пустым
      'FieldNames' => array('Id', 'Name')             // Названия параметров, которые требуется получить        
   )
);
// Преобразование входных параметров запроса в формат JSON
$body = json_encode($params, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);

// Создание контекста потока: установка HTTP-заголовков и тела запроса
$streamOptions = stream_context_create(array(
   'http' => array(
      'method' => 'POST',
      'ignore_errors' => true, // Извлечь содержимое ответа даже в случае, если HTTP-код ответа отличен от 200
      'header' => $headers,
      'content' => $body
   )
));

//--- Выполнение задачи -------------------------------------------------//
/*
Запуск цикла для выполнения запросов. Если первый запрос завершен успешно, т.е. не привел к возникновению ошибки 152, то выводится список кампаний. 
Если же первый запрос завершен с этой ошибкой, то выполняются повторные запросы с задержками, заданными в массиве set_DELAY.
*/
foreach ($set_DELAY as $currentAttempt => $delay) {
   
      // Выполнение запроса, получение результата
      $result = file_get_contents($url, 0, $streamOptions);
      
      //--- Обработка результата выполнения запроса ---------------------//
      // Извлекаем HTTP-заголовки ответа, если они есть
      if (isset($http_response_header)) {
         foreach ($http_response_header as $header) {
            if (preg_match('#HTTP/[0-9\.]+\s+([0-9]+)#', $header, $arr)) {
               $httpStr = $header; // Пояснение к HTTP-коду ответа
               $httpCode = intval($arr[1]); // Числовое значение HTTP-кода
            }
            if (preg_match('/RequestId: (\d+)/', $header, $arr)) { 
               $requestId = $arr[1]; // Идентификатор запроса
            }
            if (preg_match('/Units: (\d+)\/(\d+)\/(\d+)/', $header, $arr)) {
               $unitsSpent = $arr[1]; // Количество потраченых баллов
               $unitsAvailable = $arr[2]; // Количество доступных баллов
               $unitsLimit = $arr[3]; // Суточный лимит
            }
         }
      }
      else {
         // Если заголовков нет, значит, запрос не был выполнен - выводим ошибку
         echo date("d.m.Y H:i:s > ").'Не удалось выполнить запрос к серверу API';
      }
      
      // Обработка ответа, если получен HTTP-код
      if (isset($httpCode)) {
         // Если получен HTTP-код 200, то обрабатываем тело ответа
         if ($httpCode == 200) {            
            // Вывод информации по баллам
            if (isset($unitsSpent)) {
               echo date("d.m.Y H:i:s > ")."Израсходовано баллов за запрос: $unitsSpent Доступно сейчас: $unitsAvailable Суточный лимит: $unitsLimit<br>";
            }
            
            // Преобразование ответа из формата JSON
            $response = json_decode($result);
            
            // Если сервер API вернул ошибку в теле ответа, то выводим ее
            if (isset($response->error)) {
               $apiErr = $response->error;
               echo date("d.m.Y H:i:s > ")."Ошибка API {$apiErr->error_code}: {$apiErr->error_string} - {$apiErr->error_detail} (RequestId: {$apiErr->request_id})<br>";         
            }
         }
         else { echo date("d.m.Y H:i:s > ").'Ошибка выполнения запроса к серверу API: '.$httpStr; }
      }
      
      //--- Вывод отладочной информации ---------------------------------//
      if ($set_DEBUG) {
         echo "<hr>URL запроса: $url<br>";
         echo "Заголовки запроса: <pre>".implode($headers, '<br>')."</pre>";
         echo "Тело запроса (JSON): <pre>".json_encode($params, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)."</pre>";
         if (isset($http_response_header)) { echo "Заголовки ответа: <pre>".implode($http_response_header, '<br>')."</pre>"; }
         if (isset($response)) { echo "Тело ответа (JSON): <pre>".json_encode($response, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)."</pre>"; }
         if (isset($httpCode) and $httpCode != 200) { echo "Тело ответа: <pre>".htmlspecialchars($result)."</pre>"; }
         echo "<hr>";
      }

      //--- Обработка результата ----------------------------------------//
      // Вывод списка кампаний при наличии результата
      if (isset($response->result)) {            
         foreach ($response->result->Campaigns as $campaign) {
            echo "{$campaign->Name} (№ {$campaign->Id})<br>";
         }
         
         if ($response->result->LimitedBy) {
            // Если ответ содержит параметр LimitedBy, значит, были получены не все доступные объекты. 
            // В этом случае следует выполнить дополнительные запросы для получения всех объектов.
            // Подробное описание постраничной выборки - https://tech.yandex.ru/direct/doc/dg/best-practice/get-docpage/#page
            echo '<br>Получены не все доступные объекты.';
         }
      }

      //--- Действия при возникновении ошибки 152 "Недостаточно баллов" и ее отсутствии ---//
      if (isset($apiErr->error_code) and $apiErr->error_code == $err_NOT_ENOUGH_UNITS) {
         // Если в массиве set_DELAY значений больше, чем текущая итерация цикла, то ставим задержку перед следующей итерацией выполнения цикла
         if (isset($set_DELAY[$currentAttempt+1])) {
            unset ($apiErr); // Удаляем переменную, содержащую данные об ошибке
            echo date("d.m.Y H:i:s > ")."Повторный запрос через ".$set_DELAY[$currentAttempt+1]." секунд<br>";
            sleep ($set_DELAY[$currentAttempt+1]);
         }
      }
      // Если не была получена ошибка 152 "Недостаточно баллов", то считаем задачу выполненной и завершаем работу скрипта
      else { die(); }
}
?>