Відповіді

Кожен action контролера повертає Psr\Http\Message\ResponseInterface (PSR-7). Після Router::dispatch() ядро викликає SapiEmitter (Laminas) — заголовки та тіло потрапляють у PHP SAPI (FPM, Apache, built-in server).

Для доступу до поточного запиту в будь-якому місці програми можна використовувати сервіс RequestProxy.

Див. також: контролери, життєвий цикл → відповідь, Views (Twig).

Дві фабрики

Інтерфейс Клас Призначення
ResponseFactoryInterface Concept\Core\Http\ResponseFactory JSON, redirect, порожня відповідь, back()
ViewResponseFactoryInterface Concept\Core\Services\View\ViewResponseFactory HTML через Twig

Обидві реєструються в HttpServiceProvider. У контролері — через конструктор (автовайринг):

public function __construct(
    private readonly ResponseFactoryInterface $response,
    private readonly ViewResponseFactoryInterface $viewResponse,
) {}

HTML (Twig)

return $this->viewResponse->create('@cabinet/index', [
    'user' => $user,
    'projects' => $projects,
]);
  • @cabinet/index — логічне ім’я шаблону (префікс компонента)
  • Другий аргумент — локальні змінні для цього action
  • Третій аргумент — HTTP-код (за замовчуванням 200)

ViewResponseFactory зливає дані з атрибута запиту Concept\Core\Http\Requests\RequestAttribute::VIEW_PAYLOAD (flash, errors, old, CSRF з ShareViewDataMiddleware), потім рендерить Twig і виставляє Content-Type: text/html. Детальніше про шаблони — Представлення.

JSON

Довільний JSON — json()

return $this->response->json([
    'token' => bin2hex(random_bytes(32)),
]);

Тіло — ваш масив як є; статус за замовчуванням 200.

Узгоджений успіх — jsonSuccess()

return $this->response->jsonSuccess(['id' => $user->getId()]);

// Відповідь (приклад)
// { "status": "success", "code": 200, "data": { "id": 42 } }

Узгоджена помилка — jsonError()

return $this->response->jsonError(
    'Resource not found',
    HttpStatusCode::NOT_FOUND
);

// З масивом помилок (наприклад валідація)
return $this->response->jsonError(
    'Validation failed',
    HttpStatusCode::UNPROCESSABLE_ENTITY,
    ['email' => ['The email field is required.']]
);

// { "status": "error", "code": 422, "message": "...", "errors": { ... } }

Ключ errors додається лише якщо масив не порожній.

API-маршрути

Для JSON-запитів підключайте на групу маршрутів (за потреби): ParseJsonBodyMiddleware (body → getParsedBody()), ForceJsonResponseMiddleware (Accept/Content-Type JSON). Детальніше — middleware ядра.

RequestFormat::expectsJson() визначає, чи клієнт очікує JSON (Accept: application/json або X-Requested-With: XMLHttpRequest). Так само обробляється помилка валідації у middleware.

Редіректи

redirect($url)

return $this->response->redirect('/cabinet/profile', HttpStatusCode::FOUND);

Жорсткий шлях або повний URL. Статус за замовчуванням — 302 Found.

redirectByName()

return $this->response->redirectByName('admin.users');
return $this->response->redirectByName('admin.user.edit', ['id' => $user->getId()]);

URL будується через UrlGeneratorInterface::uri() — відносний шлях за іменованим маршрутом. Зручно після POST (PRG).

back()

Безпечний повернення «назад» (не open redirect):

  1. Referer — якщо внутрішній (той самий host або відносний /...)
  2. SAFE_BACK_URL — атрибут запиту з StorePreviousUrlMiddleware
  3. Fallback — аргумент методу (за замовчуванням /)
return $this->response->back();
return $this->response->back(HttpStatusCode::FOUND, '/cabinet');

Після невдалої валідації HandleValidationExceptionMiddleware викликає back() для HTML-форм (flash + старі дані).

Порожня та довільна відповідь

use Concept\Core\Http\Protocol\HttpStatusCode;

$response = $this->response->createResponse(HttpStatusCode::NO_CONTENT);
// або CREATED, NOT_FOUND тощо — без тіла

createResponse() — з PSR-17; для кастомного тіла запишіть у $response->getBody() і додайте заголовки через withHeader(). У звичайному коді достатньо методів фабрики вище.

Коди статусу

Константи в Concept\Core\Http\Protocol\HttpStatusCode:

КонстантаКодТипове використання
OK200Успішна відповідь
CREATED201Створення ресурсу
NO_CONTENT204Успіх без тіла
FOUND302Redirect (за замовчуванням)
NOT_FOUND404Ресурс не знайдено
UNPROCESSABLE_ENTITY422Валідація FormRequest
PAGE_EXPIRED419CSRF (CsrfException)
INTERNAL_SERVER_ERROR500Необроблена помилка

getReasonPhrase($code) — текст для reason phrase PSR-7.

Типові сценарії

POST → redirect (PRG)

public function store(StoreUserRequest $request): ResponseInterface
{
    $this->userModel->newQuery()->create(
        $this->normalizeUserPayload($request->validated())
    );
    $this->flashBag->addSuccess('User successfully created');

    return $this->response->redirectByName('admin.users');
}

Не знайдено → flash + redirect

if (!($user instanceof UserModel)) {
    $this->flashBag->addError('User not found');
    return $this->response->redirectByName('admin.users');
}

return $this->viewResponse->create('@auth-admin/users/show', ['user' => $user]);

JSON endpoint

public function generateTokenApi(): ResponseInterface
{
    return $this->response->json([
        'token' => bin2hex(random_bytes(32)),
    ]);
}

Відповіді без контролера

Частина відповідей формується в middleware або error handler:

  • ВалідаціяHandleValidationExceptionMiddleware: JSON 422 або back()
  • CSRFCsrfException (код 419), обробка error handler
  • Production / API помилки — Whoops + jsonError() за RequestFormat

Не повертайте echo або header() з контролера — лише ResponseInterface. Інакше emitter і middleware (наприклад DebugBar) не зможуть коректно обробити відповідь.