overhead view of winding road

How To: Link to Dynamic Routes in Drupal 8

Photo credit: Rich Lock

I recently ran into a situation where I needed to link directly to a Drupal page for which there was no explicit route specified in a routing YML file. In this case, I was trying to link to the entity creation page for a media entity, at media/add/image. Technically, I could’ve used Url::fromUri(), but this is not best practice. The documentation page for the Url::fromUri() method is clear about this:

This method is for generating URLs for URIs that:

-- do not have Drupal routes: both external URLs and unrouted local URIs like base:robots.txt

-- do have a Drupal route but have a custom scheme to simplify linking. Currently, there is only the entity: scheme (This allows URIs of the form entity:{entity_type}/{entity_id}. For example: entity:node/1 resolves to the entity.node.canonical route with a node parameter of 1.)

For URLs that have Drupal routes (that is, most pages generated by Drupal), use Url::fromRoute().

The correct way is to use the Url::fromRoute() method, but first we need to find the route name. It makes perfect sense that there wouldn’t be an explicit route in a routing.yml file because there can be any number of entity types on a Drupal site. Thus, there must be a dynamic means of handling routing in these types of situations. But how do we use this method when we don’t know the route name?

After exploring the node and entity modules, I began to wrap my head around how these modules provide dynamic routes. In short, the key bits can be found in: core/lib/Drupal/Core/Entity/Routing/DefaultHtmlRouteProvider.php. This class implements EntityRouteProviderInterface and has a getRoutes() method, which does the following:

Returns a route collection or an array of routes keyed by name, like route_callbacks inside 'routing.yml' files.

Here’s the code:

 /**
  * {@inheritdoc}
  */
 public function getRoutes(EntityTypeInterface $entity_type) {
   $collection = new RouteCollection();

   $entity_type_id = $entity_type->id();

   if ($add_page_route = $this->getAddPageRoute($entity_type)) {
     $collection->add("entity.{$entity_type_id}.add_page", $add_page_route);
   }

   if ($add_form_route = $this->getAddFormRoute($entity_type)) {
     $collection->add("entity.{$entity_type_id}.add_form", $add_form_route);
   }

   if ($canonical_route = $this->getCanonicalRoute($entity_type)) {
     $collection->add("entity.{$entity_type_id}.canonical", $canonical_route);
   }

   if ($edit_route = $this->getEditFormRoute($entity_type)) {
     $collection->add("entity.{$entity_type_id}.edit_form", $edit_route);
   }

   if ($delete_route = $this->getDeleteFormRoute($entity_type)) {
     $collection->add("entity.{$entity_type_id}.delete_form", $delete_route);
   }

   if ($collection_route = $this->getCollectionRoute($entity_type)) {
     $collection->add("entity.{$entity_type_id}.collection", $collection_route);
   }

   return $collection;
 }

That explains how these modules implement dynamic routes. We can now use the above to begin figuring out the route name for our example path: media/add/image.

In the case of Drupal entities, the route name is a machine name, which is constructed of three pieces:

Name of module providing the route (entity) Entity bundle ID (media) Route name (canonical, edit_form, add_form, etc.)

Thus, the route name we need is entity.media.add_form.

In my case, I was using the media module (which is moving into core in 8.4) with a custom bundle type of "image". So I also needed to pass that along to fromRoute(), like so:

Url::fromRoute('entity.media.add_form', ['media_bundle' => 'image']);

This gave me a Drupal\Core\Url object. I then used its toString() method to get the path directly.

$some_url = Url::fromRoute('entity.media.add_form', ['media_bundle' => 'image'])->toString();
var_dump($some_url);

This will output:

string(16) "/media/add/image"

I was then able to use the path string to create my link:

'#description' => $this->t('New images can be <a href="@add_media">added here</a>.', [
  '@add_media' => Url::fromRoute('entity.media.add_form', ['media_bundle' => 'image'])->toString(),
]),

Using route names as opposed to paths is not only best practice, but provides consistent means for developers to link to pages throughout Drupal. Beyond that, route names are more stable, as paths are more likely to change. Hopefully, this post helps others who want to properly link to dynamic routes in Drupal 8. It certainly had me confused.

Related Articles