HTTP Responses

Introduction

Slenix's Response class provides a clean, fluent API for building and sending HTTP responses. Every route handler receives a $response object as its second argument. You may use it to send JSON, HTML, plain text, files, redirects, or any other HTTP response.

php
use Slenix\Http\Routing\Router;

Router::get('/greet', function ($request, $response) {
    $response->json(['message' => 'Hello, World!']);
});

All response methods that configure the response (status codes, headers, cookies, cache) return $this, allowing them to be chained fluently:

php
$response
    ->status(201)
    ->withHeader('X-Custom', 'value')
    ->json(['id' => $newUser->id]);

Setting the Status Code

Use the status() method to set the HTTP status code. It accepts any valid code between 100 and 599:

php
$response->status(200); // OK
$response->status(201); // Created
$response->status(404); // Not Found
$response->status(422); // Unprocessable Entity
$response->status(500); // Internal Server Error

The status code is sent immediately via http_response_code(). Calling status() on an already-sent response has no effect.


JSON Responses

The json() method sets the Content-Type to application/json, encodes the given data, and sends the response:

php
$response->json(['name' => 'Cláudio', 'role' => 'admin']);

// With a custom status code
$response->json(['error' => 'Not found'], 404);

// With custom json_encode flags
$response->json($data, 200, JSON_PRETTY_PRINT);

Standardised Success Response

php
$response->success($user, 'User created successfully.', 201);

// Sends:
// {
//   "success": true,
//   "message": "User created successfully.",
//   "data": { ... }
// }

Standardised Error Response

php
$response->error('Validation failed.', 422, [
    'email' => 'The email field is required.',
]);

// Sends:
// {
//   "success": false,
//   "error": true,
//   "message": "Validation failed.",
//   "status_code": 422,
//   "status_text": "Unprocessable Entity",
//   "details": { "email": "..." }
// }

HTML Responses

php
$response->html('<h1>Hello, World!</h1>');

// With a custom status code
$response->html('<h1>Not Found</h1>', 404);

Rendering a View

php
$response->render('users.profile', ['user' => $user]);

// With a status code
$response->render('errors.404', [], 404);

When the global view() helper is available (registered by the application kernel), render() delegates to it automatically.


Plain Text Responses

php
$response->write('Hello, plain text!');
$response->write('Error occurred', 500);

XML Responses

php
$xml = '<?xml version="1.0"?><user><name>Cláudio</name></user>';
$response->xml($xml);

// With a SimpleXMLElement
$element = new SimpleXMLElement('<user><name>Cláudio</name></user>');
$response->xml($element, 201);

Redirects

php
// Temporary redirect (302)
$response->redirect('/dashboard');

// Permanent redirect (301)
$response->redirect('/new-url', 301);

// Redirect back to the previous page
$response->redirectBack();

// Redirect back with a fallback URL
$response->redirectBack('/home');

Supported redirect status codes: 301, 302, 303, 307, 308. Passing any other code throws an InvalidArgumentException.

All redirect URLs are automatically sanitized against header injection attacks (newlines and null bytes are stripped).


File Downloads

The download() method streams a file to the client in 8 KB chunks, keeping memory usage flat regardless of file size:

php
$response->download('/storage/reports/q3.pdf');

// With a custom file name
$response->download('/storage/reports/q3.pdf', 'Q3-Report-2025.pdf');

// With a custom MIME type
$response->download('/storage/data.csv', 'export.csv', 'text/csv');

// Inline (display in browser instead of downloading)
$response->download('/storage/invoice.pdf', 'Invoice.pdf', null, true);

If the file does not exist or cannot be read, a 404 error response is sent automatically.


Headers

php
// Set a single header
$response->withHeader('X-Request-ID', 'abc-123');

// Set multiple headers at once
$response->withHeaders([
    'X-Request-ID' => 'abc-123',
    'X-Version'    => '1.0',
]);

// Remove a header
$response->withoutHeader('X-Powered-By');

// Read a header that was set
$value = $response->getHeader('X-Request-ID');

// Get all set headers
$all = $response->getHeaders();

All header names and values are automatically sanitized against header injection.


Cookies

php
// Basic cookie
$response->withCookie('user_token', $token, time() + 86400);

// With options
$response->withCookie('theme', 'dark', 0, [
    'path'     => '/',
    'domain'   => '.example.com',
    'secure'   => true,
    'httponly' => true,
    'samesite' => 'Lax',
]);

// Maximum security cookie (secure, httponly, SameSite=Strict)
$response->withSecureCookie('session_token', $token, time() + 3600);

// Delete a cookie
$response->withoutCookie('user_token');

// Read all cookies set on this response
$cookies = $response->getCookies();

Cache Control

php
// Cache for 1 hour (public — can be cached by CDN and browser)
$response->withCache(3600);

// Cache for 5 minutes, private (browser only, not CDN)
$response->withCache(300, false);

// Disable caching entirely
$response->withoutCache();

withCache() sets the Cache-Control, Expires, and Last-Modified headers automatically.


CORS Headers

php
// Allow all origins with default settings
$response->withCors();

// Custom CORS configuration
$response->withCors([
    'origin'      => 'https://app.example.com',
    'methods'     => 'GET, POST, PUT, DELETE',
    'headers'     => 'Content-Type, Authorization',
    'credentials' => true,
    'max_age'     => 86400,
]);

Chaining Example

Methods that configure the response return $this, allowing you to chain multiple calls before sending:

php
$response
    ->status(201)
    ->withHeader('X-Resource-ID', (string) $user->id)
    ->withCors(['origin' => 'https://app.example.com'])
    ->withoutCache()
    ->json([
        'success' => true,
        'data'    => $user,
    ]);

Sending the Response Manually

In most cases you call a terminal method (json, html, redirect, etc.) which internally calls send(). If you need to build a response object and send it later, you can do so manually:

php
$res = Response::make('Hello!', 200);
$res->withHeader('X-Custom', 'yes');
$res->send();

send() outputs the body and calls exit. Calling it on an already-sent response is a no-op.


Inspecting the Response

php
$code = $response->getStatusCode(); // 200
$text = $response->getStatusText(); // 'OK'
$body = $response->getBody();       // raw content
$sent = $response->isSent();        // bool

Method Reference

MethodReturnsDescription
status(code)selfSet HTTP status code
json(data, status, flags)voidSend JSON response
success(data, message, status)voidStandardised success JSON
error(message, status, details)voidStandardised error JSON
html(html, status)voidSend HTML response
write(text, status)voidSend plain text response
xml(xml, status)voidSend XML response
render(template, data, status)voidRender a view template
redirect(url, status)voidRedirect to URL
redirectBack(fallback, status)voidRedirect to previous page
download(path, name, type, inline)voidStream a file download
withHeader(name, value)selfSet a response header
withHeaders(array)selfSet multiple headers
withoutHeader(name)selfRemove a header
getHeader(name, default)mixedRead a set header
getHeaders()arrayAll set headers
withContentType(type, charset)selfSet Content-Type
withCookie(name, value, expire, opts)selfSet a cookie
withSecureCookie(name, value, expire, opts)selfSet a secure cookie
withoutCookie(name, opts)selfDelete a cookie
getCookies()arrayAll cookies set
withCache(maxAge, public)selfSet cache headers
withoutCache()selfDisable caching
withCors(options)selfSet CORS headers
send(content, status)voidSend the full response
sendHeaders()selfSend headers only
getStatusCode()intCurrent status code
getStatusText()stringStatus text for current code
getBody()mixedRaw response body
isSent()boolWhether response was sent
Response::make(content, status)selfStatic factory