Ответы
Контроллер возвращает объект ответа, а не пишет в поток руками. Чаще всего
это ResponseEntity — данные с HTTP-кодом. Фреймворк сам сериализует его в нужный
формат и отправляет; для файлов и HTML есть отдельные типы.
Что такое ответ и зачем
Ответ — это то, что уходит клиенту: HTTP-код, заголовки и тело.
Проблема. Если формировать ответ вручную (выставить статус, сериализовать
данные, записать заголовки, закрыть соединение), код замусоривается транспортными
деталями, а под FPM и Swoole они ещё и разные. Легко забыть Content-Type или
перепутать код.
Решение. Верните из метода объект ответа — ResponseEntity для данных,
ResponseView для HTML, ResponseFile для файлов. Фреймворк сам выставит код,
согласует формат с клиентом и отправит ответ одинаково под любым рантаймом. Об
этом и раздел.
ResponseEntity — данные
Основной тип ответа для API. Статические фабрики задают HTTP-код, тело сериализуется автоматически:
return ResponseEntity::ok($data); // 200
return ResponseEntity::created($data); // 201
return ResponseEntity::accepted($data); // 202
return ResponseEntity::noContent(); // 204
return ResponseEntity::badRequest('message'); // 400
return ResponseEntity::unauthorized(); // 401
return ResponseEntity::forbidden(); // 403
return ResponseEntity::notFound('message'); // 404
return ResponseEntity::conflict($data); // 409
return ResponseEntity::unprocessable($data); // 422
return ResponseEntity::internalError(); // 500Произвольный код и заголовки — билдер-стилем:
use Flytachi\Winter\Base\HttpCode;
return ResponseEntity::status(HttpCode::ACCEPTED)->body($data);
return ResponseEntity::ok($data)->header('X-Request-Id', $requestId);Согласование формата
Когда тело — массив или объект, формат выбирается по заголовку Accept клиента:
Accept |
Формат |
|---|---|
application/json |
JSON |
application/xml |
XML |
text/html или */* / отсутствует |
JSON (дефолт API) |
Скаляры (string/int/float/bool) всегда уходят как text/plain. Объект с
методом toArray() конвертируется перед согласованием.
Можно вернуть просто массив
Если вернуть не-Sendable значение, роутер обернёт его сам: return ['key' => 'value'] эквивалентно ResponseEntity::ok(['key' => 'value']). Возврат null
или void закрывает соединение без тела (для явного 204 лучше noContent()).
ResponseFile — файлы и выгрузки
Готовые фабрики под частые форматы. Все выставляют Content-Length и отключают
сжатие, чтобы не повредить содержимое:
use Flytachi\Winter\K2\Http\Response\ResponseFile;
ResponseFile::binary($data, 'report.bin'); // произвольные байты
ResponseFile::txt($content, 'readme.txt'); // текст
ResponseFile::csv($rows, 'export.csv'); // массив строк → CSV
ResponseFile::json($array, 'data.json'); // массив или готовая строка
ResponseFile::xml($data, 'feed.xml'); // SimpleXML/stdClass/array/скаляр
ResponseFile::file('/var/report.pdf'); // файл с диска, MIME автоБилдер настраивает отдачу:
return ResponseFile::csv($rows, 'export.csv')
->attachment() // Content-Disposition: attachment (диалог скачивания)
->inline() // Content-Disposition: inline (показать в браузере)
->maxAge(3600) // Cache-Control: public, max-age=3600
->header('X-Source', 'generated');По умолчанию скачиваются (attachment) binary и csv; остальные — inline.
ResponseStreamFile — большие файлы
ResponseFile::file() читает файл в память целиком. Для больших файлов (видео,
аудио, архивы, дампы) используйте ResponseStreamFile — он стримит с диска
(zero-copy на Swoole) и работает как корректный файловый сервер:
use Flytachi\Winter\K2\Http\Response\ResponseStreamFile;
return ResponseStreamFile::open('/var/media/video.mp4'); // inline
return ResponseStreamFile::open('/var/export/report.pdf')->attachment(); // скачатьОн поддерживает HTTP Range (частичная отдача 206 — перемотка <video>,
докачка), условные запросы (304 по ETag/Last-Modified) и валидаторы
If-Range. Отключить Range на эндпоинте: ->acceptRanges(false).
ResponseFile::file() |
ResponseStreamFile::open() |
|
|---|---|---|
| Читается в память | целиком | нет (стрим) |
HTTP Range / 206 |
нет | да |
Условный GET / 304 |
нет | да |
| Для чего | мелкие файлы | большие файлы, медиа |
HTML — ResponseView
Серверный рендеринг PHP-шаблонов — отдельная тема со своими хелперами и макетами. См. страницу Представления.
Свои ответы — Sendable
Sendable — общий контракт всех типов ответа. Реализуйте его для полностью
кастомной отдачи:
use Flytachi\Winter\K2\Http\Response\Sendable;
use Flytachi\Winter\K2\Http\Contracts\HttpRequest;
use Flytachi\Winter\K2\Http\Contracts\HttpResponse;
class CsvResponse implements Sendable
{
public function __construct(private string $csv, private string $filename) {}
public function send(HttpResponse $response, HttpRequest $request): void
{
$response->status(200);
$response->header('Content-Type', 'text/csv; charset=utf-8');
$response->header('Content-Disposition', "attachment; filename=\"{$this->filename}\"");
$response->end($this->csv);
}
}Встроенные Sendable: ResponseEntity, ResponseFile, ResponseStreamFile,
ResponseView.
Под капотом
Как объект ответа превращается в байты на проводе. Для повседневной работы знать не обязательно.
- Единый контракт
HttpResponse. Каждый ответ пишет через абстракцию (SwooleResponse/FpmResponse) с методамиstatus(),header(),end(),sendfile(). Поэтому один и тот же код ответа работает под Swoole и FPM. send(HttpResponse, HttpRequest). Метод получает и запрос — чтобы согласовать формат, обработать Range или условный GET. Тем, кому запрос не нужен, просто игнорируют аргумент.HEAD— централизованно. Тело дляHEADподавляет адаптерHttpResponse, а не каждый ответ: те же статус и заголовки (включаяContent-Length), но без тела. Роутер при этом не маппитHEADнаGET— нужен явный маршрут.
Дальше
- Представления — серверный HTML через
ResponseView - Обработка ошибок — ответы для исключений
- Запросы и привязка — входная сторона цикла