Dynamic routing is a crucial aspect of Drupal's flexible and extensible architecture. It enables developers to define their own routes dynamically, which can be highly beneficial in certain situations.
Recently, I had to generate dynamic routes for several custom plugins and was able to implement this using Drupal's dynamic routing concepts.
Context
I was developing a wrapper module with multiple utility plugins, so I defined a plugin type within the module to allow for dynamic creation of these plugins. Later, a requirement arose for unique routes for each plugin, to present the plugin form to users. To accomplish this, I utilized Drupal's dynamic routing feature.
Implementation
1. Add 'route_callbacks'
in modules routing.yml
route_callbacks:
- '\Drupal\webutilities\Routing\RoutesProvider::routes'
2. Create a routing class
Here route {plugin}
parameter is casted to webutility
(Custom plugin type) plugin.
<?php
namespace Drupal\webutilities\Routing;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\webutilities\WebUtilityPluginManager;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;
/**
* Provides dynamic routes of plugins.
*/
class RoutesProvider implements ContainerInjectionInterface {
/**
* Plugin manager service.
*
* @var \Drupal\webutilities\WebUtilityPluginManager
*/
protected $pluginManager;
/**
* RoutesProvider constructor.
*/
public function __construct(WebUtilityPluginManager $webUtilityPluginManager) {
$this->pluginManager = $webUtilityPluginManager;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('plugin.manager.webutility')
);
}
/**
* {@inheritdoc}
*/
public function routes() {
$route_collection = new RouteCollection();
$plugins = $this->pluginManager->getDefinitions();
foreach ($plugins as $key => $plugin) {
$route_id = "webutilities.{$key}";
$route = new Route(
// Path to attach this route to:
'module/{plugin}',
// Route defaults:
[
'_controller' => '\Drupal\webutilities\Controller\ModuleDisplayController::moduleDisplay',
'_title_callback' => '\Drupal\webutilities\Controller\ModuleDisplayController::getPluginTitle',
'type' => 'webutility',
],
// Route requirements:
[
'_permission' => 'access utilities modules',
],
[
'parameters' => [
'plugin' => ['type' => 'webutility'],
],
]
);
$route_collection->add($route_id, $route);
}
return $route_collection;
}
}
3. Add display controller
<?php
namespace Drupal\webutilities\Controller;
use Drupal\Component\Utility\Html;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\Extension\ModuleExtensionList;
use Drupal\Core\Form\FormBuilderInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\Url;
use Drupal\webutilities\Utility\Utility;
use Drupal\webutilities\WebUtilityEntityInterface;
use Drupal\webutilities\WebUtilityInterface;
use Drupal\webutilities\WebUtilityPluginManager;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Request;
/**
* Handles utilities module list.
*/
class ModuleDisplayController extends ControllerBase implements ContainerInjectionInterface {
use StringTranslationTrait;
/**
* Plugin manager service.
*
* @var \Drupal\webutilities\WebUtilityPluginManager
*/
protected $pluginManager;
/**
* Module extension list service.
*
* @var \Drupal\Core\Extension\ModuleExtensionList
*/
protected $moduleExtensionList;
/**
* ModuleDisplayController constructor.
*/
public function __construct(WebUtilityPluginManager $webUtilityPluginManager,
ModuleExtensionList $moduleExtensionList,
FormBuilderInterface $formBuilder) {
$this->pluginManager = $webUtilityPluginManager;
$this->moduleExtensionList = $moduleExtensionList;
$this->formBuilder = $formBuilder;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('plugin.manager.webutility'),
$container->get('extension.list.module'),
$container->get('form_builder')
);
}
/**
* Render plugin form.
*
* @param \Drupal\webutilities\WebUtilityInterface $plugin
* Plugin object.
* @param \Symfony\Component\HttpFoundation\Request $request
* Current request.
*
* @return array
* Render-able array.
*/
public function moduleDisplay(WebUtilityInterface $plugin, Request $request) {
$body = NULL;
$config = Utility::loadPluginConfig($plugin->getPluginId());
if ($config instanceof WebUtilityEntityInterface) {
$body = $config->getContent();
if (isset($body['value'])) {
$body = [
'#type' => 'processed_text',
'#text' => @$body['value'],
'#format' => 'full_html',
];
}
}
$form = $this->formBuilder->getForm('\Drupal\webutilities\Form\WebUtilityDynamicForm', $plugin);
$build = [
'#theme' => 'module_form',
'#form' => $form,
'#body' => $body,
];
return $build;
}
/**
* Return Plugin Title.
*
* @param \Drupal\webutilities\WebUtilityInterface $plugin
* Plugin object.
* @param \Symfony\Component\HttpFoundation\Request $request
* Current request.
*/
public function getPluginTitle(WebUtilityInterface $plugin, Request $request) {
$defination = $plugin->getPluginDefinition();
$module_path = $this->moduleExtensionList->getPath($defination['provider']);
return [
'#type' => 'inline_template',
'#template' => '<img width="50" src="{{ logo }}" class="module-img" alt="{{ title }}" /> {{ title }}',
'#context' => [
'logo' => "/{$module_path}/assets/images/{$plugin->getPluginId()}.svg",
'title' => $plugin->label(),
],
];
}
}
There are different ways to implement dynamic routing in Drupal. One way is by using the method described above. Another option is to develop a Route Subscriber (event_subscriber). However, the method described above has worked well for me.