We know that starting with Drupal 8 we have a good Routing system. But I am faced with one limitation, I cannot define the route with the parameter in the first position ("/{param_1}/some/path"). And in this article I want to share a solution for this case.
Let's imagine that we have a list of articles, each of which belongs to a certain category (taxonomy term). I want to have a router for such a path "/{category}/{article_alias}/{view_mode}".
The list of categories can be changed at any time, so we cannot determine all possible routes in advance. The view_mode is just an additional parameter for demonstration.
Fortunately, Drupal allows us to declare our custom Routers. Let's create a new module for this.
modules/custom/dynamic_router/dynamic_router.info.yml:
name: Dynamic Router
type: module
description: 'Provides Dynamic routes for article CT.'
dependencies:
- drupal:node
package: Custom
core_version_requirement: ^11
modules/custom/dynamic_router/dynamic_router.routing.yml:
route_callbacks:
- '\Drupal\dynamic_router\Routing\DynamicRouter::routes'
We usually provide several custom routes in this file, but we can also define route_callback.
modules/custom/dynamic_router/src/Routing/DynamicRouter.php:
<?php
namespace Drupal\dynamic_router\Routing;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Routing\Route;
/**
* Class provides dynamic routing for the articles.
*/
class DynamicRouter implements ContainerInjectionInterface {
/**
* Constructs DynamicRouter class.
*
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager service.
*/
public function __construct(
protected EntityTypeManagerInterface $entityTypeManager
) {}
/**
* {@inheritDoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('entity_type.manager')
);
}
/**
* Returns an array of route objects.
*
* @return \Symfony\Component\Routing\Route[]
* An array of route objects.
*/
public function routes(): array {
$routes = [];
$terms = $this->entityTypeManager->getStorage('taxonomy_term')
->loadTree("article_categories", 0, NULL, TRUE);
/** @var \Drupal\taxonomy\TermInterface $category */
foreach ($terms as $category) {
$category_path = $category->path->alias;
$routes['category.' . $category->id() . '.article'] = new Route(
$category_path . '/{node}/{view_mode}',
['_controller' => 'Drupal\dynamic_router\Controller\ArticleController::node'],
['_permission' => 'access content'],
);
}
return $routes;
}
}
For this example, I created a new taxonomy vocabulary with the machine name "article_categories"
And a simple controller, just to demonstrate, I returned the standard output of node builder using view_mode parameter from route.
modules/custom/dynamic_router/src/Controller/ArticleController.php:
<?php
namespace Drupal\dynamic_router\Controller;
use Drupal\Core\Controller\ControllerBase;
use Drupal\path_alias\AliasManagerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\node\NodeInterface;
class ArticleController extends ControllerBase {
/**
* The entity type manager service.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* Constructs a ArticleController object.
*
* @param \Drupal\Core\Path\AliasManagerInterface $aliasManager
* The alias manager service.
*/
public function __construct(
protected AliasManagerInterface $aliasManager
) {}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('path_alias.manager'),
);
}
/**
* Renders a node as a render array.
*
* @param string $node
* The node alias.
* @param string $view_mode
* View mode ('full', 'part').
*
* @return array
* A render array for the node.
*/
public function node(string $node, string $view_mode): array {
$path = $this->aliasManager->getPathByAlias('/' . $node);
if (preg_match('/^\\/node\\/(\\d+)$/', $path, $matches)) {
$nid = $matches[1];
$node = $this->entityTypeManager()->getStorage('node')->load($nid);
}
if ($node instanceof NodeInterface && $node->isPublished()) {
$view_builder = $this->entityTypeManager->getViewBuilder('node');
return $view_builder->view($node, $view_mode);
}
else {
return [
'#markup' => $this->t('Node not found or unpublished.'),
];
}
}
}
Now we can check if Drupal already knows about our routes.
> drush core:route | grep category
And now, if we add a new term to the vocabulary of article categories, the list of routes will be updated automatically.
That's all! Please respond if you have any thoughts on this solution and good luck :)
Top comments (0)