Quick Reference#

Cheat sheet for common Webrick operations.


Request#

// Method
$method = $r->getMethod();
$r->isGet();  $r->isPost();

// URI
$path = $r->getPath();              // '/users/42'
$uri = $r->getUri();                // 'https://example.com/users/42'
$query = $r->getQueryString();      // 'page=2'

// Query params
$page = $r->query('page', 1);
$all = $r->query();

// Headers
$type = $r->getHeaderLine('Content-Type');
$accept = $r->getHeader('Accept');  // Array

// Body
$json = $r->json();                 // Parsed JSON
$data = $r->input();                // Any parsed body
$raw = $r->getContent();            // Raw string

// Files
$file = $r->file('upload');
$file->moveTo('/uploads/' . $file->getClientFilename());

// Cookies
$session = $r->cookie('session');

// Attributes
$userId = $r->getAttribute('auth.user_id');
$r = $r->withAttribute('key', 'value');

Response#

// JSON
Response::json(['id' => 42]);
Response::json(['error' => 'Not found'], 404);

// Text
Response::plaintext('Hello World', 200);

// HTML
Response::create('<h1>Title</h1>', 200, ['Content-Type' => 'text/html; charset=UTF-8']);

// Empty
Response::noContent();              // 204

// Redirect
Response::redirect('/login');
Response::redirect('/new-url', 301);
Response::redirect(Route::urlFor('users.show', ['id' => 42]));

// Download
Response::download('/path/to/file.pdf');
Response::download('/path/to/file.pdf', 'report.pdf');

// Headers
$response->withHeader('X-Custom', 'value');
$response->withAddedHeader('Set-Cookie', $cookie);
$response->withoutHeader('X-Debug');

// Status
$response->withStatus(404);
$response->getStatusCode();

Routes#

// Basic
Route::get('/users', fn() => Response::json([]));
Route::post('/users', fn(Request $r) => Response::json($r->input(), 201));
Route::put('/users/{id:int}', fn(int $id) => Response::json(['id' => $id]));
Route::delete('/users/{id:int}', fn(int $id) => Response::noContent());

// With name
Route::get('/users/{id:int}', $handler, 'users.show');

// With middleware
Route::get('/admin', $handler, ['middleware' => ['auth', 'admin']]);

// Groups
Route::group(prefix: '/api', middleware: ['throttle:60,60'], callback: function() {
    Route::get('/users', $handler);
});

// Parameters
Route::get('/users/{id:int}', fn(int $id) => /* ... */);
Route::get('/posts/{slug:slug}', fn(string $slug) => /* ... */);
Route::get('/items/{uuid:uuid}', fn(string $uuid) => /* ... */);

Middleware#

// Closure
$middleware = function (Request $r, Closure $next): Response {
    $r = $r->withAttribute('start', microtime(true));
    $response = $next($r);
    $duration = microtime(true) - $r->getAttribute('start');
    return $response->withHeader('X-Time', $duration . 'ms');
};

// Class
final class AuthMiddleware {
    public function __invoke(Request $r, Closure $next): Response {
        if (!$this->isAuthenticated($r)) {
            return Response::json(['error' => 'Unauthorized'], 401);
        }
        return $next($r);
    }
}

// Register alias
MiddlewareAliases::register('auth', fn() => new AuthMiddleware());

// Use
Route::get('/protected', $handler, ['middleware' => ['auth']]);

URL Generation#

// From named route
$url = Route::urlFor('users.show', ['id' => 42]);
// '/users/42'

// Absolute
$url = Route::urlFor('users.show', ['id' => 42], absolute: true);
// 'https://example.com/users/42'

// With query
$url = Route::urlFor('users.index', query: ['page' => 2]);
// '/users?page=2'

Signed URLs#

// Sign
$signed = Route::signedUrlFor('download.file', ['file' => 'report.pdf']);

// Temporary
$temp = Route::temporaryUrlFor('download.file', ['file' => 'report.pdf'], ttl: 3600);

Common Patterns#

RESTful CRUD#

Route::get('/posts', [PostController::class, 'index'], 'posts.index');
Route::post('/posts', [PostController::class, 'store'], 'posts.store');
Route::get('/posts/{id:int}', [PostController::class, 'show'], 'posts.show');
Route::put('/posts/{id:int}', [PostController::class, 'update'], 'posts.update');
Route::delete('/posts/{id:int}', [PostController::class, 'destroy'], 'posts.destroy');

API with Auth#

Route::group(prefix: '/api', middleware: ['throttle:120,60'], callback: function() {
    // Public
    Route::post('/login', [AuthController::class, 'login']);

    // Protected
    Route::group(middleware: ['auth'], callback: function() {
        Route::get('/profile', [ProfileController::class, 'show']);
        Route::put('/profile', [ProfileController::class, 'update']);
    });
});

File Upload#

Route::post('/upload', function(Request $r) {
    $file = $r->file('document');

    if (!$file || $file->getError() !== UPLOAD_ERR_OK) {
        return Response::json(['error' => 'Upload failed'], 400);
    }

    $filename = bin2hex(random_bytes(16)) . '.' . pathinfo($file->getClientFilename(), PATHINFO_EXTENSION);
    $file->moveTo('/uploads/' . $filename);

    return Response::json(['filename' => $filename], 201);
});

Paginated API#

Route::get('/api/users', function(Request $r) {
    $page = (int) $r->query('page', 1);
    $perPage = 20;

    $users = UserRepository::paginate($page, $perPage);
    $total = UserRepository::count();

    return Response::json([
        'data' => $users,
        'pagination' => [
            'total' => $total,
            'per_page' => $perPage,
            'current_page' => $page,
            'last_page' => ceil($total / $perPage),
        ]
    ]);
});

HTTP Status Codes#

200  OK
201  Created
204  No Content
301  Moved Permanently
302  Found
304  Not Modified
400  Bad Request
401  Unauthorized
403  Forbidden
404  Not Found
422  Unprocessable Entity
429  Too Many Requests
500  Internal Server Error
503  Service Unavailable