The goal is to provide a dynamic (wildcard) route that should match on all nested routes that starts from some string.
The main issue is that Drupal routing system, does not provide a clean way to describe a wildcard routes that should match some kind of pattern, like /UserGuide
, /UserGuide/Development_Notes
or /UserGuide/Release_Notes/3.0.x
etc.
Initial idea is to use dynamic routes, that will work in case when routes are known or could be generated by some pattern, like /UserGuide/node/1
, /UserGuide/node/2
etc. Otherwise this is not possible, like when the route could consist from the multiple undefined parts.
As one possible solution, I decided to try the inbound path processor to catch destination path to redirect to the page controller, and pass an original path as a query parameter.
File example/example.routing.yml:
example.user_guide:
path: '/UserGuide'
defaults:
_controller: '\Drupal\example\Controller\ExamplePageController::view'
_title: 'User Guide'
requirements:
_access: 'TRUE'
File example/example.services.yml:
services:
example.path_processor:
class: Drupal\example\PathProcessor\ExamplePathProcessor
tags:
- { name: path_processor_inbound, priority: 1000 }
File example/src/PathProcessor/ExamplePathProcessor.php:
<?php
namespace Drupal\example\ExamplePathProcessor;
use Drupal\Core\PathProcessor\InboundPathProcessorInterface;
use Symfony\Component\HttpFoundation\Request;
// Before PHP 8, use a polyfill.
// @see https://www.php.net/manual/en/function.str-starts-with.php
if (!function_exists('str_starts_with')) {
function str_starts_with($haystack, $needle) {
return (string) $needle !== '' && strncmp($haystack, $needle, strlen($needle)) === 0;
}
}
class ExamplePathProcessor implements InboundPathProcessorInterface {
/**
* {@inheritdoc}
*/
public function processInbound($path, Request $request) {
if (!str_starts_with($path, '/UserGuide')) return $path;
// Remove leading slash.
$original_path = ltrim(str_replace('/UserGuide', '', $path), '/');
$request->query->set('original_path', $original_path);
return '/UserGuide';
}
}
File example/src/Controller/ExamplePageController.php:
<?php
namespace Drupal\example\Controller;
use Drupal\Core\Controller\ControllerBase;
class ExamplePageController extends ControllerBase {
public function view() {
$request_query = \Drupal::request()->query->all();
// Get an original path.
$original_path = $request_query['original_path'];
// Do anything else...
// For example, try to fetch page content from some external API etc.
$page_content = self::getPageContent($original_path);
if (empty($page_content)) {
// Return 404 not found if page does not exist.
throw new \Symfony\Component\HttpKernel\Exception\NotFoundHttpException();
}
// Return a renderable array with a page content.
return $build;
}
}