# Drupal: Get the current entity

First things first, it’s not always possible to get the *current* entity, some routes will have more than 1 entity, some wont have any, `drush` wont give a route. But most of the time, what you want when talking about the *current* entity is the entity being viewed by a canonical (or other entity links) route.

## Snippet

What you’re looking for is most likely this:

```php
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Routing\RouteMatch;

/**
 * Get the route entity (defaults to current route).
 */
function currentEntity(?RouteMatch $route_match = NULL): ?EntityInterface {
  $route_match = $route_match ?? \Drupal::routeMatch();

  foreach ($route_match->getParameters() as $key => $value) {
    if (
      $value instanceof EntityInterface
      // Make sure the route path matches a link in the entity.
      && in_array($route_match->getRouteObject()->getPath(), $value->getEntityType()->getLinkTemplates())
    ) {
      return $value;
    }
  }
}
```

I’m not the first to wonder or write about this, check out this forum topic: [*Drupal 8: get entity object given system path?*](https://www.drupal.org/forum/support/module-development-and-code-questions/2014-07-25/drupal-8-get-entity-object-given). And my solution is a lot like [*Get the entity for a route*](https://www.computerminds.co.uk/drupal-code/get-entity-route) which also appears in the previous link.

The code is straight forward, it checks if the parameter is an [`EntityInterface`](https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Entity%21EntityInterface.php/interface/EntityInterface/11.x), and if the entity has the route path as a link, a [`Node`](https://api.drupal.org/api/drupal/core%21modules%21node%21src%21Entity%21Node.php/class/Node/11.x) would have these `/node/{node}`, `/node/{node}/edit`**,** `/node/{node}/delete` and a bunch more depending on what modules you have.

## One route two entities

The problem is some routes have more than 1 entity in the parameters, like the `entity.node.revision` route that takes 2 entities `/node/{node}/revisions/{node_revision}/view`, the first being the node, and the second being a specific revision of the node. A route could have many more parameter entities, but it seems rare to encounter more than 2.  
Sometimes a route will have 1 entity, but the path is not in the entity links, this usually occurs when it’s a custom route (at least in my cases).

With multiple entities only the *primary* entity is needed. For this to work need some business logic, that will get us the correct entity.  
Using the `entity.node.revision` as an example I would argue that the entity you wanted is the `node_revision`, but the `currentEntity()` function would simple return the first entity it encountered.  
The same is true for an path without an entity link, we need some kind of project specific logic to handle this.

So custom business logic, that can change from project to project. I’d say there’re 3 obvious solutions for this, a [plugin](https://www.drupal.org/docs/drupal-apis/plugin-api) system, an [event based](https://api.drupal.org/api/drupal/core%21core.api.php/group/events/11.x) system or [hooks](https://api.drupal.org/api/drupal/core%21core.api.php/group/hooks/11.x). Plugins seems overkill, and hooks are *persona non grata*, so at the moment let’s fix it using events (I would probably have used hooks if it was just me, it’s by far a smaller and easier implementation).

### The fix

I don’t want this turning into a how to do events post, so I’ll leave a lot of the parts out, you should read the [Subscribe to and dispatch events](https://www.drupal.org/docs/develop/creating-modules/subscribe-to-and-dispatch-events) article for any background on this.  
I’ve added proof of concept module in following repo [https://github.com/BirkAndMe/poc-primary\_entity](https://github.com/BirkAndMe/poc-primary_entity), which I’ll link to, but I’m also going to include any code of interest in the post.

```php
function primary_entity(?RouteMatchInterface $route_match = NULL): ?EntityInterface {
  $route_match = $route_match ?? \Drupal::routeMatch();

  /** @var EventDispatcherInterface*/
  $event_dispatcher = \Drupal::service('event_dispatcher');

  $event = new PrimaryEntityEvent($route_match);
  $event_dispatcher->dispatch($event, PrimaryEntityEvent::GET);

  return $event->hasPrimaryEntity() ? $event->getPrimaryEntity() : NULL;
}
```

The idea is you’ll call the [`primary_entity()`](https://github.com/BirkAndMe/poc-primary_entity/blob/main/primary_entity.module) function and it will return the current / primary [`EntityInterface`](https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Entity%21EntityInterface.php/interface/EntityInterface/11.x) for the given route. The [`primary_entity()`](https://github.com/BirkAndMe/poc-primary_entity/blob/1e2877694fd89da6528fecf24f81e4fb1f723728/primary_entity.module#L12) function dispatches a [`PrimaryEntityEvent::GET`](https://github.com/BirkAndMe/poc-primary_entity/blob/main/src/Event/PrimaryEntityEvent.php) event, and the [`PrimaryEntitySubscriber`](https://github.com/BirkAndMe/poc-primary_entity/blob/main/src/EventSubscriber/PrimaryEntitySubscriber.php) subscribes to the event:

```php
  public static function getSubscribedEvents(): array {
    return [
      PrimaryEntityEvent::GET => [
        ['checkRevision', 110],
        ['checkLinkTemplates', 100]
      ],
    ];
  }
```

Note the [`PrimaryEntitySubscriber::checkRevision()`](https://github.com/BirkAndMe/poc-primary_entity/blob/1e2877694fd89da6528fecf24f81e4fb1f723728/src/EventSubscriber/PrimaryEntitySubscriber.php#L26) has a higher priority, it needs to run first, or the [`PrimaryEntitySubscriber::checkLinkTemplates()`](https://github.com/BirkAndMe/poc-primary_entity/blob/1e2877694fd89da6528fecf24f81e4fb1f723728/src/EventSubscriber/PrimaryEntitySubscriber.php#L48) function will return the entity and not the current revision when viewing revision routes.

The `PrimaryEntitySubscriber::checkLinkTemplates()` is doing almost the same as the `currentEntity()` function above, but lets take quick look at the `PrimaryEntitySubscriber::checkRevision()` that checks to see if the route is a revision route:

```php
  public function checkRevision(PrimaryEntityEvent $event) {
    if (!$event->getRouteMatch()->getRouteObject()) {
      return;
    }

    $entities = $event->getEntities();
    $parameters = $event->getRouteMatch()->getRouteObject()->getOption('parameters') ?? [];

    foreach ($parameters as $key => $parameter) {
      if (
        !empty($parameter['type'])
        && strpos($parameter['type'] ?? '', 'entity_revision:') === 0
        && isset($entities[$key])
      ) {
        return $this->setPrimaryEntity($event, $entities[$key]);
      }
    }
  }
```

The [`PrimaryEntityEvent::getEntities()`](https://github.com/BirkAndMe/poc-primary_entity/blob/1e2877694fd89da6528fecf24f81e4fb1f723728/src/Event/PrimaryEntityEvent.php#L39) is a helper function that will look through the route parameters in the event, and return all route parameters of `EntityInterface` type.  
We assume that if there’s a parameter type with `entity_revision:*` it’s the entity we want. I would guess this is the expected behavior in most use cases.

To override this behavior you would need to create another event subscriber, that subscribes to [`PrimaryEntityEvent::GET`](https://github.com/BirkAndMe/poc-primary_entity/blob/main/src/Event/PrimaryEntityEvent.php) with a higher priority than *110* which is what the `checkRevision()` is set to. And simply add more event subscribers to add any custom business logic, you can use the following template (that will return a specific parameter depending on a specific route):

```php
<?php

use Drupal\primary_entity\Event\PrimaryEntityEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

/**
 *
 */
class PrimaryEntityEventExample implements EventSubscriberInterface {

  /**
   *
   */
  public function checkCustomRoute(PrimaryEntityEvent $event): void {
    if ($event->getRouteMatch()->getRouteName() === 'some.routename') {
      $event
        ->setPrimaryEntity($event->getRouteMatch()->getParameter('entity_parameter_name'))
        ->stopPropagation();
    }
  }

  /**
   * {@inheritdoc}
   */
  public static function getSubscribedEvents(): array {
    return [
      PrimaryEntityEvent::GET => 'checkCustomRoute'
    ];
  }

}
```

## Conclusion

Getting the current entity is a trivial problem 95% of the time, but the last 5% needs a dedicated module with custom per project logic to work.

I have a variant of the first `currentEntity()` function I copy from project to project. It is a bit hackish, but it made sure all my *guess current entity* logic was in one place, so I (and others) could quickly get an overview.

But after diving a bit deeper into it all I think I’ll include the [primary entity module](https://github.com/BirkAndMe/poc-primary_entity) in our base tools, and use it as described here instead.
