Leading and trailing whitespaces. The bane of normalized data everywhere. It seems like a constant task that needs to be performed on incoming user data. When working with Laravel, I use the TrimStrings
middleware. Calls PHP's built-in trim
function on all string values for the request query and input values – including JSON! While working on my project WhiskeyDex, I noticed how mobile device keyboards always add trailing whitespace when using the autocomplete functionality. This, in turn, meant user-created data will end up with trailing whitespaces. I started to just quick-fix in the single instance and realized it should be solved once and for all.
You can read the Middleware API documentation if you are unfamiliar with HTTP middleware in Drupal or as a concept. Drupal uses Symfony's HttpKernel component to handle incoming requests and outgoing responses. HTTP middleware services implement the HttpKernelInterface
interface and register themselves as services tagged with http_middleware
. When Drupal handles a request, it invokes all registered http_middleware
to process the request. For instance, Drupal's PageCache
is an HTTP middleware to immediately return responses from the cache if available.
Drupal does not provide as many developer-friendly tools as Laravel, so I needed to recreate my version of the TrimStrings
middleware. It's relatively simple, requiring one class and its service registration. Note: the following code is for PHP 8.1.
In your module, create a src/StackMiddleware/TrimMiddleware.php
file with the following:
<?php
declare(strict_types=1);
namespace Drupal\whiskeydex\StackMiddleware;
use Symfony\Component\HttpFoundation\InputBag;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\HttpKernelInterface;
final class TrimMiddleware implements HttpKernelInterface {
public function __construct(
private readonly HttpKernelInterface $app
) {
}
public function handle(
Request $request,
int $type = self::MAIN_REQUEST,
bool $catch = TRUE): Response {
$this->trimBag($request->query);
$this->trimBag($request->request);
return $this->app->handle($request);
}
private function trimBag(InputBag $bag): void {
$bag->replace($this->trimArray($bag->all()));
}
private function trimArray(array $array): array {
return array_map(
function ($value) {
if (is_array($value)) {
return $this->trimArray($value);
}
return is_string($value) ? trim($value) : $value;
},
$array
);
}
}
The real work is in trimArray
, which iterates through the array, and any nested ones, returning trimmed strings.
Then register the service in your module's services.yml
:
services:
http_middleware.trim:
class: Drupal\whiskeydex\StackMiddleware\TrimMiddleware
arguments: ['@kernel']
tags:
- { name: http_middleware }
Here is a link to a gist of the code as well: https://gist.github.com/mglaman/4533051f0ec50ebd698bbde93c1ac79b
Want more? Sign up for my weekly newsletter