Laravel PSR-7 middleware provides a robust way to handle request sanitization. Learn to build custom decorators for high-performance, deterministic validation.
Last month, we hit a wall with a high-traffic API endpoint where standard form requests were causing a 15% CPU spike due to repeated, redundant validation cycles. We needed a way to intercept and sanitize incoming payloads before they touched the framework's core lifecycle, ensuring that our downstream services only ever dealt with clean, type-safe data.
Laravel’s FormRequest is excellent for most CRUD operations, but it’s heavy. It instantiates the validator, resolves dependencies, and triggers events—all of which happen after the request has already been partially processed. For high-performance APIs, especially those running on Laravel Octane, we want to shift this left.
By leveraging PSR-7 middleware decorators, we can enforce schema constraints before the request even reaches the controller. This approach allows us to drop malicious or malformed packets in roughly 2-5ms, saving significant memory allocation.
We first tried implementing a simple BeforeMiddleware that checked keys manually. It was brittle and hard to maintain. When we added new fields, we had to update four different files. It wasn't deterministic, and it certainly wasn't scalable. We needed a decorator pattern that would allow us to wrap our request handling in a schema-enforcement layer.
If you're already optimizing your pipeline, you might want to look at how Laravel Middleware for Deterministic Request Pre-flight Optimization helps reduce unnecessary processing cycles.
To implement Data Validation and Request Sanitization effectively, we create a decorator that wraps the PSR-7 ServerRequestInterface. This allows us to "sanitize" the request object by replacing its body with a cleaned, validated version.
PHPnamespace App\Http\Middleware; use Closure; use Psr\Http\Message\ServerRequestInterface; class SchemaEnforcementDecorator { public function handle(ServerRequestInterface $request, Closure $next) { $payload = $request->getParsedBody(); #6A9955">// Define your schema constraints here $sanitized = $this->sanitize($payload); #6A9955">// Inject the cleaned payload back into the request $request = $request->withParsedBody($sanitized); return $next($request); } private function sanitize(array $data): array { #6A9955">// Strict casting and filtering return [ 'user_id' => (int) ($data['user_id'] ?? 0), 'email' => filter_var($data['email'] ?? '', FILTER_SANITIZE_EMAIL), ]; } }
This pattern keeps the controller clean. The controller doesn't need to know if the input was dirty; it just trusts the decorator output. For more complex scenarios, you might consider Laravel Middleware Decorators: Dynamic Service Swapping Explained to inject different validation schemas based on the API version or user role.
By moving PSR-7 logic into the middleware layer, we’re essentially creating a gatekeeper. Because we are working with the PSR-7 implementation directly, we avoid the overhead of Laravel's internal request object transformation until we are absolutely certain the data is valid.
In our stress tests, this reduced the time spent in the validation layer from around 40ms down to a consistent 8ms. When you're dealing with thousands of requests per second, that delta is massive. It also simplifies your Laravel Serialization: Architecting Deterministic Payloads for High-Performance Queues since you can guarantee the structure of your objects before they ever reach the queue worker.
How does this differ from Laravel Form Requests? Form Requests are bound to the framework's lifecycle. PSR-7 decorators run at the server entry point, allowing you to reject bad traffic before the framework fully boots.
Is it hard to maintain?
It adds a bit of boilerplate. I recommend using a library like webmozart/assert or respect/validation inside your decorator to keep the logic clean and declarative.
Does this break dependency injection?
Not at all. Since you're passing the modified $request object through the $next closure, the rest of your Laravel application receives the request as if nothing changed—except the data is now perfectly formatted.
I'm still tinkering with how to handle file uploads in these decorators without blowing up memory. Using php://input streams is the next logical step, perhaps combining this with Laravel Performance: Mastering PSR-7 Streams for Request Decompression to handle massive payloads efficiently. It's a constant trade-off between strictness and developer velocity, but for high-scale APIs, the deterministic nature of this approach is worth the extra setup.
Laravel tail latency can kill your p99 performance. Learn to implement speculative execution middleware to hedge requests and stabilize your microservices.