0

Form Request classes — custom request validation and authorization

Intermediate5 min read·lv-09-006
interview

Concept

Request macros and custom Request classes allow extending and specializing the Request object for application-specific needs.

Macros: Request::macro('methodName', function() { ... }) adds a new method to all Request instances. Defined in a service provider. The $this inside the macro closure refers to the Request instance.

Custom Request class: Create a class extending Illuminate\Http\Request and override capture() or use in Form Requests. Most useful for adding typed accessor methods.

Testing requests: In feature tests, request-related testing works through $this->get(), $this->post(), etc. For unit testing middleware, use Request::create() to instantiate a request programmatically.

Request::create(): Static factory for creating Request objects without an actual HTTP request. Useful in tests and queue jobs that need to process request-like data.

Precognitive requests: Laravel Precognition allows front-end validation without submitting the form. Requests include Precognition: true header. $request->isPrecognitive() detects this.

$request->merge(array $data): Add or override input values. Useful in middleware to add computed fields: $request->merge(['user_timezone' => 'UTC']).

$request->replace(array $data): Replace all input with the given data.

Code Example

php
<?php
// Request macros — in AppServiceProvider::boot()
use Illuminate\Http\Request;

Request::macro('userTimezone', function(): string {
    // 'this' is the Request instance
    return $this->header('X-User-Timezone', config('app.timezone'));
});

Request::macro('apiVersion', function(): string {
    return $this->segment(2, 'v1'); // URL: /api/v2/... → "v2"
});

Request::macro('isPaidUser', function(): bool {
    return $this->user()?->subscription?->isActive() ?? false;
});

// Usage in controller
public function index(Request $request)
{
    $tz      = $request->userTimezone();  // uses macro
    $version = $request->apiVersion();    // uses macro
}

// Programmatic Request creation — for testing
$request = Request::create(
    '/api/orders',
    'POST',
    ['status' => 'pending'],           // body parameters
    [],                                // cookies
    [],                                // files
    ['HTTP_AUTHORIZATION' => 'Bearer test_token'], // server vars
    json_encode(['cart_id' => 1])      // raw body content
);

// Request merging in middleware
class AddComputedFields
{
    public function handle(Request $request, Closure $next): mixed
    {
        $request->merge([
            'request_id' => (string) \Illuminate\Support\Str::uuid(),
            'ip_country' => $this->geoip->getCountry($request->ip()),
        ]);
        return $next($request);
    }
}

// Custom request class for type safety
class ApiRequest extends Request
{
    public function apiVersion(): string
    {
        return $this->segment(2, 'v1');
    }

    public function paginationLimit(): int
    {
        return min((int) ($this->input('per_page', 15)), 100);
    }
}