Responses & Error Handling

Craft HTTP responses, stream files, and surface friendly errors.

Category: HTTP Stack

Response helpers #

VelvetCMS\Http\Response wraps status code, headers, and body. Static constructors cover common cases:

Helper Description
Response::html($html, $status = 200) Sets Content-Type: text/html.
Response::json($payload, $status = 200) JSON-encodes arrays/objects with JSON_THROW_ON_ERROR.
Response::redirect($url, $status = 302) Empty body + Location header.
Response::file($path, $mime = null, $headers = []) Streams files with auto MIME detection and length headers.
Response::notFound($message = 'Not Found') Convenience 404.
Response::methodNotAllowed($allowedVerbs) Returns 405 + Allow header.

Chain withHeader() / withHeaders() / withStatus() to tweak the instance before calling send().

JSON vs HTML #

Controllers/closures can simply return ['status' => 'ok'] or return '<h1>Hi</h1>'; the router normalizes results into Response objects:

  • Arrays/objects → JSON
  • Strings → HTML
  • Existing Response → passed through untouched

This keeps route handlers terse while still letting you handcraft responses when needed.

Exception handler pipeline #

VelvetCMS\Exceptions\Handler (bound to ExceptionHandlerInterface) centralizes error reporting and rendering:

  1. ErrorHandlingMiddleware wraps the entire middleware/controller stack and forwards exceptions to the handler.
  2. Handler::report() fires exception.reporting, tries custom reporters, and falls back to PSR-3 logging.
  3. Handler::render() fires exception.rendering, then checks for:
    • RenderableExceptionInterface implementations with their own toResponse().
    • Registered renderers keyed by class name.
    • HttpException instances (already know the status).
    • Generic fallback: JSON or HTML message depending on Request::expectsJson().
  4. app.debug=true toggles verbose stack traces (HTML or JSON) instead of the generic “Server Error.”

Creating custom errors #

  • Implement RenderableExceptionInterface for domain-specific faults and throw them anywhere; the handler will call toResponse().
  • Implement ReportableExceptionInterface to customize logging/alerting per exception.
  • Register additional renderers or reporters by calling $handler->addRenderer(FooException::class, fn ($e, $request) => Response::json(...)); inside a service provider/module.

Validation feedback #

When Request::validate() fails it throws ValidationException, which ships with helper methods for structured responses. You can catch it inside controllers or let the exception handler convert it into JSON/HTML depending on the request type.

Production readiness checklist #

  • Keep ErrorHandlingMiddleware as the first global middleware so everything else benefits.
  • Ensure config('app.debug') is false in production to avoid leaking stack traces.
  • Configure a PSR-3 logger binding (Monolog, Sentry, etc.) so Handler::report() writes somewhere useful.
  • Consider adding a middleware before errors that maps domain exceptions to friendly pages (maintenance, feature flags, etc.).

With these primitives you can standardize how APIs, controllers, and modules return data while still offering plenty of hooks for custom behavior.