Запрос и ответ
Что пришло от браузера и что уходит обратно: параметры, заголовки, формат, файлы, редиректы.
Два компонента приложения. request разбирает входящий HTTP-запрос, response собирает исходящий. Действие контроллера может вернуть строку, массив или объект ответа — остальное фреймворк сделает сам.
Чтение запроса
$request = Yii::$app->request;
$request->get('id'); // ?id=7, второй аргумент — значение по умолчанию
$request->post('name', 'аноним');
$request->getBodyParams(); // всё тело, разобранное парсером
$request->getRawBody(); // как есть, для подписи вебхуков
$request->getQueryParams();
$request->isPost; $request->isGet; $request->isAjax; $request->isPjax;
$request->method; // GET, POST, PUT…
$request->url; // /post/7?src=ad
$request->absoluteUrl;
$request->referrer; $request->userAgent; $request->userIP;
$request->isSecureConnection;
$request->headers->get('X-Api-Key');
$request->getAuthCredentials(); // [логин, пароль] из Basic
$request->getPreferredLanguage(['ru-RU', 'en-US']);Формирование ответа
return $this->render('view', ['model' => $model]); // HTML в layout
return $this->renderPartial('_row', ['item' => $item]); // без layout
return $this->asJson(['ok' => true]); // JSON
return $this->redirect(['post/view', 'id' => 7]);
return $this->goBack(); // на returnUrl
return $this->goHome();
return $this->refresh();
$response = Yii::$app->response;
$response->statusCode = 201;
$response->headers->set('X-Total-Count', $count);
$response->format = Response::FORMAT_JSON;
$response->data = ['items' => $items];
return $response->sendFile('/path/report.pdf');
return $response->sendContentAsFile($csv, 'export.csv');
return $response->xSendFile('/path/big.zip'); // отдаёт веб-серверопции: JsonParser, MultipartFormDataParser
'request' => [
'parsers' => [
'application/json' => yii\web\JsonParser::class,
],
],опции: длинная случайная строка вне репозитория
'request' => [
'cookieValidationKey' => getenv('COOKIE_KEY'),
],опции: csrfParam, csrfCookie
// точечно отключить для вебхука
public function beforeAction($action)
{
if ($action->id === 'webhook') {
$this->enableCsrfValidation = false;
}
return parent::beforeAction($action);
}опции: secureHeaders, ipHeaders, hostInfo
'request' => [
'trustedHosts' => ['10.0.0.0/8'],
'hostInfo' => 'https://example.com',
],$response->format = Response::FORMAT_HTML;опции: JsonResponseFormatter: prettyPrint, encodeOptions
'response' => ['formatters' => [
'json' => [
'class' => yii\web\JsonResponseFormatter::class,
'prettyPrint' => YII_DEBUG,
'encodeOptions' => JSON_UNESCAPED_UNICODE,
],
]],опции: XmlResponseFormatter: rootTag, itemTag
'xml' => [
'class' => yii\web\XmlResponseFormatter::class,
'rootTag' => 'response',
],опции: data = [data, callback]
$response->format = Response::FORMAT_JSONP;
$response->data = ['data' => $rows, 'callback' => 'cb'];$response->format = Response::FORMAT_RAW;
$response->content = $svg;
$response->headers->set('Content-Type', 'image/svg+xml');опции: BadRequest 400, Unauthorized 401, Forbidden 403, NotFound 404, MethodNotAllowed 405, Conflict 409, UnprocessableEntity 422, TooManyRequests 429, ServerError 500
throw new NotFoundHttpException('Пост не найден');
throw new ForbiddenHttpException('Нет доступа');
throw new BadRequestHttpException();
// сообщение видно пользователю и не в debug-режиме
throw new UserException('Заказ уже оплачен');опции: событие Response
'response' => [
'on beforeSend' => function ($event) {
$r = $event->sender;
$t = microtime(true) - YII_BEGIN_TIME;
$r->headers->set('X-Runtime', round($t, 3));
},
],пример
parsers
Разбор тела запроса по типу содержимого. Без этого JSON от клиента не попадёт в post().
опции: JsonParser, MultipartFormDataParser
'request' => [
'parsers' => [
'application/json' => yii\web\JsonParser::class,
],
],Одинаковый формат ошибок для API
'response' => [
'class' => yii\web\Response::class,
'on beforeSend' => function ($event) {
$response = $event->sender;
if ($response->format !== yii\web\Response::FORMAT_JSON) {
return;
}
$response->data = [
'success' => $response->isSuccessful,
'status' => $response->statusCode,
'data' => $response->data,
];
},
],Отдача файла, которого нет на диске
public function actionExport()
{
$csv = "id;name\n";
foreach (User::find()->batch(500) as $users) {
foreach ($users as $user) {
$csv .= $user->id . ';' . $user->username . "\n";
}
}
return Yii::$app->response->sendContentAsFile($csv, 'users.csv', [
'mimeType' => 'text/csv',
]);
}Разный ответ на обычный и AJAX-запрос
public function actionDelete($id)
{
$this->findModel($id)->delete();
if (Yii::$app->request->isAjax) {
Yii::$app->response->statusCode = 204;
return null;
}
Yii::$app->session->setFlash('success', 'Удалено');
return $this->redirect(['index']);
}граблиJSON пришёл, а post() пустой
Без parsers тело с типом application/json не разбирается. Добавьте JsonParser в компонент request.
граблиВывод после return
Любой echo или лишний пробел вне тегов PHP попадёт в тело ответа и сломает JSON и отдачу файлов. Не ставьте закрывающий ?> в конце файлов классов.
вниманиеsendFile не завершает скрипт
После sendFile() код продолжит выполняться. Всегда возвращайте результат из действия, а не вызывайте метод посреди логики.
граблиНеверный IP за прокси
Без trustedHosts компонент request не доверяет заголовкам X-Forwarded-For, и userIP покажет адрес балансировщика, а isSecureConnection вернёт false даже на HTTPS.