Form Request classes — custom request validation and authorization
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
// 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);
}
}