Skip to main content

Trimming whitespace from Drupal form submissions using an HTTP middleware

Published on

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

#