Responses & Error Handling
Craft HTTP responses, stream files, and surface friendly errors.
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:
ErrorHandlingMiddlewarewraps the entire middleware/controller stack and forwards exceptions to the handler.Handler::report()firesexception.reporting, tries custom reporters, and falls back to PSR-3 logging.Handler::render()firesexception.rendering, then checks for:RenderableExceptionInterfaceimplementations with their owntoResponse().- Registered renderers keyed by class name.
HttpExceptioninstances (already know the status).- Generic fallback: JSON or HTML message depending on
Request::expectsJson().
app.debug=truetoggles verbose stack traces (HTML or JSON) instead of the generic “Server Error.”
Creating custom errors #
- Implement
RenderableExceptionInterfacefor domain-specific faults and throw them anywhere; the handler will calltoResponse(). - Implement
ReportableExceptionInterfaceto 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
ErrorHandlingMiddlewareas the first global middleware so everything else benefits. - Ensure
config('app.debug')isfalsein 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
errorsthat 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.