Entity access in routes

Now that we understand how entity-level access control works, let's return to routes for a moment. If you remember, I mentioned the _entity_access route requirement and how we would talk about it once we had covered entity access.

The _entity_access route requirement is nothing more than a service-based access checker, much like the one we wrote ourselves. However, it is created by the entity system in order to control access to routes based on dynamic entity parameters in those routes. Let's see a quick example of a route definition that can use the _entity_access requirement:

products.view_product: 
  path: '/our-products/{product}' 
  defaults: 
    _controller: '\Drupal\products\Controller\ProductsController::showProduct' 
  requirements: 
    _entity_access: 'product.view' 
  options: 
    parameters: 
      product: 
        type: 'entity:product' 

This route has a dynamic parameter called product. In the options, we map this parameter to the Product entity type, so that our Controller method (showProduct()) already receives the loaded product entity instead of just the ID. An added benefit of this is that if the product is not found, a 404 is thrown for us. Since this route is clearly dependent on that particular product, we also want to make sure that it can be accessible only if the user has access to view that product.

One way we can ensure access is to add a permission requirement that matches the one for viewing the Product entities. However, this is not a good idea for two reasons:

An alternative way to counter these problems is to implement an access checker service and check for the access on the entity inside that service:

$access = $entity->access('view', $account);  

However, there's a lot of boilerplate setup involved for just this line of code. We'd have to do so for all entity types and operations.

Instead, we use the built-in _entity_access access checker as in the example route definition. Instead of TRUE (what we've been using for our access checker), this one actually expects a value it will make use of, and that is a string with two parts separated by a period (.). The first part is the entity type, whereas the second is the operation. Under the hood, EntityAccessCheck will look in the route parameters and check for the found entity's access using the provided operation. Easy peasy.