HTTP Responses

ZephyrPHP provides a comprehensive Response class for creating HTTP responses, from simple text to JSON APIs, file downloads, and streaming.

Creating Responses

Create responses in multiple ways:

use ZephyrPHP\Core\Http\Response;

// Basic response
$response = new Response('Hello, World!');

// With status code
$response = new Response('Not Found', 404);

// With headers
$response = new Response('Hello', 200, [
    'X-Custom-Header' => 'value'
]);

// Using static factory
$response = Response::make('Hello, World!', 200);

// Using fluent builder
$response = Response::create('Content')
    ->status(200)
    ->header('X-Custom', 'value')
    ->build();

Sending Responses

// Send response
$response->send();

// Send and terminate script
$response->sendAndExit();

Content Types

JSON Responses

// Basic JSON
return Response::json([
    'name' => 'John',
    'email' => 'john@example.com'
]);

// With status code
return Response::json(['error' => 'Not found'], 404);

// Pretty printed JSON (debugging)
return Response::jsonPretty($data);

// JSONP response
return Response::jsonp($data, 'callback');

HTML & Text Responses

// HTML response
return Response::html('<h1>Hello World</h1>');

// Plain text
return Response::text('Hello World');

// XML response
return Response::xml('<root><item>data</item></root>');

HTTP Status Helpers

Success Responses

// 201 Created
return Response::created($user);

// With location header
return Response::created($user, '/api/users/' . $user->id);

// 202 Accepted
return Response::accepted($data);

// 204 No Content
return Response::noContent();

Error Responses

// 400 Bad Request
return Response::badRequest('Invalid input');

// 401 Unauthorized
return Response::unauthorized('Please login');

// 403 Forbidden
return Response::forbidden('Access denied');

// 404 Not Found
return Response::notFound('User not found');

// 405 Method Not Allowed
return Response::methodNotAllowed(['GET', 'POST'], 'Method not allowed');

// 409 Conflict
return Response::conflict('Resource already exists');

// 410 Gone
return Response::gone('Resource no longer available');

// 422 Validation Error
return Response::validationError([
    'email' => ['The email has already been taken.'],
    'password' => ['The password must be at least 8 characters.']
]);

// With custom message
return Response::validationError($errors, 'Please fix the errors below');

// 429 Too Many Requests
return Response::tooManyRequests(60, 'Rate limit exceeded');

// 500 Server Error
return Response::serverError('Something went wrong');

// 503 Service Unavailable
return Response::serviceUnavailable(300, 'Maintenance in progress');

// Generic error
return Response::error('Custom error', 400);

JSON API Helpers

Standardized API response format:

// Success response
return Response::success($data, 'Operation completed');
/*
{
    "success": true,
    "message": "Operation completed",
    "data": { ... }
}
*/

// Failure response
return Response::fail($errors, 'Operation failed');
/*
{
    "success": false,
    "message": "Operation failed",
    "errors": { ... }
}
*/

// Paginated response
return Response::paginated(
    $users,         // data array
    $totalCount,    // total records
    $perPage,       // items per page
    $currentPage    // current page number
);
/*
{
    "data": [...],
    "meta": {
        "total": 100,
        "per_page": 15,
        "current_page": 1,
        "last_page": 7,
        "from": 1,
        "to": 15
    }
}
*/

Redirects

// Basic redirect
$response = Response::redirectTo('/dashboard');
$response->send();

// Or direct redirect (terminates script)
(new Response())->redirect('/dashboard');

// Permanent redirect (301)
return Response::permanentRedirect('/new-url');

// Temporary redirect (307)
return Response::temporaryRedirect('/temp-url');

// Redirect to named route
return Response::route('users.show', ['id' => $user->id]);

// External redirect
return Response::away('https://external-site.com');

// Redirect back
(new Response())->back();

// Back with fallback
(new Response())->back('/home');

Headers

$response = new Response('Hello');

// Set single header
$response->setHeader('X-Custom-Header', 'value');
$response->header('X-Custom-Header', 'value'); // alias

// Set multiple headers
$response->withHeaders([
    'X-Custom-One' => 'value1',
    'X-Custom-Two' => 'value2'
]);

// Check header exists
if ($response->hasHeader('Content-Type')) { }

// Get header value
$contentType = $response->getHeader('Content-Type');

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

// Remove header
$response->removeHeader('X-Custom-Header');

Cookies

$response = new Response('Hello');

// Set a cookie
$response->cookie('name', 'value');

// With expiration (in minutes)
$response->cookie('remember', 'token', 60 * 24 * 30); // 30 days

// With all options
$response->cookie(
    'session_id',     // name
    'abc123',         // value
    60,               // minutes
    '/',              // path
    'example.com',    // domain
    true,             // secure (HTTPS only)
    true,             // httpOnly
    'Strict'          // sameSite
);

// Forever cookie (5 years)
$response->cookieForever('preferences', 'value');

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

// Clear all cookies
$response->clearCookies();

File Downloads

$response = new Response();

// Download a file
$response->download('/path/to/file.pdf');

// With custom filename
$response->download('/path/to/file.pdf', 'invoice.pdf');

// With custom headers
$response->download('/path/to/file.pdf', 'report.pdf', [
    'X-Download-Options' => 'noopen'
]);

// Stream download (for generated content)
$response->streamDownload(function() {
    echo generateCsvContent();
}, 'export.csv');

File Display

$response = new Response();

// Display file inline (in browser)
$response->file('/path/to/image.jpg');

// With custom filename
$response->file('/path/to/document.pdf', 'preview.pdf');

Streaming Responses

$response = new Response();

$response->stream(function() {
    echo "data: Starting...\n\n";
    flush();

    for ($i = 0; $i < 100; $i++) {
        echo "data: Progress $i%\n\n";
        flush();
        usleep(50000);
    }

    echo "data: Done!\n\n";
});

Caching

$response = new Response('Content');

// Set cache duration (seconds)
$response->cache(3600);          // 1 hour public cache
$response->cache(3600, false);   // 1 hour private cache

// Prevent caching
$response->noCache();

// Set ETag
$response->etag(md5($content));
$response->etag($hash, weak: true); // Weak ETag

// Set Last-Modified
$response->lastModified(new DateTime('2024-01-15 10:30:00'));

// Check if modified (for 304 responses)
if ($response->isNotModified($request)) {
    return $response->setStatusCode(304)->setContent('');
}

CORS Support

$response = Response::json($data);

// Add CORS headers
$response->withCors(
    origins: '*',                                    // or ['https://example.com']
    methods: ['GET', 'POST', 'PUT', 'DELETE'],
    headers: ['Content-Type', 'Authorization'],
    credentials: false,
    maxAge: 86400
);

// Handle preflight request
return Response::corsPreflightResponse(
    origins: 'https://myapp.com',
    methods: ['GET', 'POST'],
    headers: ['Content-Type', 'X-Custom-Header'],
    credentials: true
);

Security Headers

$response = new Response($content);

// Add common security headers
$response->withSecurityHeaders();
/*
Adds:
- X-Content-Type-Options: nosniff
- X-Frame-Options: SAMEORIGIN
- X-XSS-Protection: 1; mode=block
- Referrer-Policy: strict-origin-when-cross-origin
*/

// Content Security Policy
$response->contentSecurityPolicy([
    'default-src' => ["'self'"],
    'script-src' => ["'self'", 'https://cdn.example.com'],
    'style-src' => ["'self'", "'unsafe-inline'"],
    'img-src' => ["'self'", 'data:', 'https:'],
]);

// HTTP Strict Transport Security (HSTS)
$response->hsts(
    maxAge: 31536000,           // 1 year
    includeSubdomains: true,
    preload: true
);

Status Checks

$response = new Response('', 200);

// Status category checks
$response->isInformational(); // 1xx
$response->isSuccessful();    // 2xx
$response->isRedirection();   // 3xx
$response->isClientError();   // 4xx
$response->isServerError();   // 5xx

// Specific status checks
$response->isOk();            // 200
$response->isForbidden();     // 403
$response->isNotFound();      // 404
$response->isEmpty();         // 204 or 304

// Get status info
$code = $response->getStatusCode();
$text = $response->getStatusText();

Response Builder

Fluent interface for building responses:

$response = Response::create()
    ->content('Hello World')
    ->status(200)
    ->header('X-Custom', 'value')
    ->headers([
        'X-Another' => 'value2',
        'X-Third' => 'value3'
    ])
    ->cookie('session', 'abc123', 60)
    ->build();

// Build and send
Response::create()
    ->content($html)
    ->status(200)
    ->send();

Complete API Example

app/Controllers/Api/UserController.php
<?php

namespace App\Controllers\Api;

use ZephyrPHP\Core\Controllers\Controller;
use ZephyrPHP\Core\Http\Response;
use App\Models\User;

class UserController extends Controller
{
    public function index()
    {
        $page = $this->request->integer('page', 1);
        $perPage = $this->request->integer('per_page', 15);

        $users = User::query()
            ->limit($perPage)
            ->offset(($page - 1) * $perPage)
            ->all();

        $total = User::query()->count();

        return Response::paginated($users, $total, $perPage, $page)
            ->withCors()
            ->cache(300);
    }

    public function store()
    {
        if (!$this->request->filled(['name', 'email'])) {
            return Response::validationError([
                'name' => ['Name is required'],
                'email' => ['Email is required']
            ]);
        }

        $user = new User();
        $user->fill($this->request->only(['name', 'email']));
        $user->save();

        return Response::created($user, '/api/users/' . $user->id);
    }

    public function show(int $id)
    {
        $user = User::find($id);

        if (!$user) {
            return Response::notFound('User not found');
        }

        return Response::success($user);
    }

    public function destroy(int $id)
    {
        $user = User::find($id);

        if (!$user) {
            return Response::notFound('User not found');
        }

        $user->delete();

        return Response::noContent();
    }
}

Status Codes Reference

Code Status Description
200OKRequest successful
201CreatedResource created
202AcceptedRequest accepted for processing
204No ContentSuccess with no body
301Moved PermanentlyPermanent redirect
302FoundTemporary redirect
304Not ModifiedUse cached version
400Bad RequestInvalid request
401UnauthorizedAuthentication required
403ForbiddenAccess denied
404Not FoundResource not found
405Method Not AllowedHTTP method not allowed
422Unprocessable EntityValidation error
429Too Many RequestsRate limit exceeded
500Internal Server ErrorServer error
503Service UnavailableService temporarily unavailable

Method Reference

Method Description
json($data, $status)Create JSON response
success($data, $message)JSON success response
fail($errors, $message)JSON failure response
paginated(...)Paginated JSON response
created($data, $location)201 Created response
noContent()204 No Content response
validationError($errors)422 validation error
redirectTo($url)Redirect response
download($path, $name)File download response
file($path)Inline file response
stream($callback)Streaming response
withCors(...)Add CORS headers
withSecurityHeaders()Add security headers
cache($seconds)Set cache headers
cookie(...)Set response cookie