Ми}{@лbI4

Блог хеллоуворлдщика

Yii2, REST, ContentNegotiator и ErrorHandler

23.10.2015 rest, api, yii2

Проблема "невозможности" указать формат ответа у ошибок, которые, к примеру были вызваны вне контроллера и не могут быть "пропущены" через yii\filters\ContentNegotiator имеет место быть, т.к. в сети по этому поводу 0, а в тех болванках, что предназначены для демонстрации возможностей Yii2 в плане REST из коробки, все решение данного вопроса сводилось к тому, чтобы просто явно указать формат ответа в yii\web\Response, что вообще, я считаю, костыль, т.к. весь смысл отдачи ответа в разных форматах сразу продает.

Решение оказалось очень простым, просто нужно внимательно читать документацию к классам, а не бегать глазами по ней. В классе yii\filters\ContentNegotiator черным по белому написано:

 * The following code shows how you can use ContentNegotiator as a bootstrapping component. Note that in this case,
 * the content negotiation applies to the whole application.
 *
 * // in application configuration
 * use yii\web\Response;
 *
 * return [
 *     'bootstrap' => [
 *         [
 *             'class' => 'yii\filters\ContentNegotiator',
 *             'formats' => [
 *                 'application/json' => Response::FORMAT_JSON,
 *                 'application/xml' => Response::FORMAT_XML,
 *             ],
 *             'languages' => [
 *                 'en',
 *                 'de',
 *             ],
 *         ],
 *     ],
 * ];

, что в переводе: "если тебе, парниш, нужен ровный, пацанский REST - пихай меня в boostrap своего приложения, и все будет шикарно". Именно так, нам и нужно сделать, но перед этим не забудьте убрать ContentNegotiator из списка поведений в контроллере.

Далее, есть еще такой момент. Дефолтный yii\web\ErrorHandler, я считаю, нужно напильником немного. Данный ErrorHandler заменяет код ответа на 500, если исключение не имеет ничего общего с yii\web\HttpException, т.е. экземпляром класса, но, и кроме этого, в случае запроса не указанного или неизвестного формата ответа кидается исключение yii\web\UnsupportedMediaTypeHttpException, которое отдает ответ в HTML формата. Лично я "заглушил" контент ответа на это исключение и вернул только код ответа в заголовке, т.к. раз мы не знает такой формат, то и нечего что-то отдавать. Это все можно сделать так:

namespace app\components\rest;
 
use Yii;
use yii\web\Response;
use yii\web\UnsupportedMediaTypeHttpException;
 
/**
 * @inheritdoc
 */
class ErrorHandler extends \yii\web\ErrorHandler
{
    /**
     * @inheridoc
     */
    protected function renderException($exception)
    {
        if (Yii::$app->has('response')) {
            $response = Yii::$app->getResponse();           
            // reset parameters of response to avoid interference with partially created response data
            // in case the error occurred while sending the response.
            $response->isSent = false;
            $response->stream = null;
            $response->data = null;
            $response->content = null;
        } else {
            $response = new Response();
        }
        if ($exception instanceof UnsupportedMediaTypeHttpException) {
            $response->data = '';
        } else {
            $response->data = $this->convertExceptionToArray($exception);
        }
        $response->setStatusCode($exception->statusCode);
        $response->send();
    }
}

Теперь все равно и по пацански. На районе оценят.