4

I have something like the following on my .routing.yml file:

my.path:
  path: '/my-path/{node}'
  defaults:
    _form: 'Drupal\mymodule\Form\MyForm'
  requirements:
    _entity_access: 'node.update'
    node: \d+

This works fine, it checks that the user has permissions to edit the node and all. Now, I want to restrict this route to work only for nodes of a certain content type. Ideally, something like this as the final line:

    node.bundle: 'article'   # this doesn't work, of course

How can I do this?

  • You can restrict it in MyForm . You can check the content type there – Shreya Shetty Nov 30 '16 at 5:31
  • You have to create tagged access check service. Search for "_node_add_access" in node module to see what you need to do and how to use it. It's quite easy actually. – user21641 Nov 30 '16 at 8:07
10

I could find three possible ways. Supposing your module name is example and content type to check is article.

1. Validating the argument in the form builder

It is the simplest way.

First define your route as usual:

example.routing.yml

 example.node.edit_form:
  path: /example/{node}/edit
  defaults:
    _form: 'Drupal\example\Form\ExampleNodeForm'
  options:
    _node_operation_route: TRUE
    parameters:
      node:
        type: entity:node
  requirements:
    _permission: 'edit any article content'

Then simply put this lines to Drupal\example\Form\ExampleNodeForm::buildForm before the other code.

if ($node->getType() != 'article') {
  throw new AccessDeniedHttpException();
}

2. Validate the argument using custom access callback

The route definition is the same as above except custom_access option under the requirements section.

example.routing.yml

 example.node.edit_form:
  path: /example/{node}/edit
  defaults:
    _form: 'Drupal\example\Form\ExampleNodeForm'
  options:
    _node_operation_route: TRUE
    parameters:
      node:
        type: entity:node
  requirements:
    _permission: 'edit any article content'
    _custom_access:  'Drupal\example\Form\ExampleNodeForm::access'

Drupal\example\Form\ExampleNodeForm::access() should return instance of Drupal\Core\Access\AccessResult. This can be done as follows.

return AccessResult::allowedIf($node->getType() == 'article');

3. Validation the argument with access checker

example.routing.yml

 example.node.edit_form:
  path: /example/{node}/edit
  defaults:
    _form: 'Drupal\example\Form\ExampleNodeForm'
  options:
    _node_operation_route: TRUE
    parameters:
      node:
        type: entity:node
  requirements:
    _permission: 'edit any article content'
    _content_type:  'article'

As you can see this includes _content_type requirement. To make it work you need to implement custom access check service.

example.services.yml

services:
  example.content_type_access_checker:
    class: Drupal\example\Access\ContentTypeAccessCheck
    tags:
      - { name: access_check, applies_to: _content_type }

Drupal\example\src\Access\ContentTypeAccessCheck.php

namespace Drupal\example\Access;

use Drupal\Core\Access\AccessResult;
use Drupal\Core\Routing\Access\AccessInterface;
use Drupal\node\NodeInterface;
use Symfony\Component\Routing\Route;

/**
 * Checks if node type matches the one provided in the route configuration.
 */
class ContentTypeAccessCheck implements AccessInterface {

  /**
   * Access callback.
   */
  public function access(Route $route, NodeInterface $node) {
    return AccessResult::allowedIf($node->getType() == $route->getRequirement('_content_type'));
  }
}

The last approach can be helpful if you want to reuse access control code accross multiple routes.

Documentation: https://www.drupal.org/docs/8/api/routing-system/access-checking-on-routes.

  • 5
    Great answer. One thing to note is that I would recommend approach 2 over 1 whenever possible. 2. allows Drupal to check access to that page before visiting it. For example, if you have a local task for that route on nodes, it will automatically only show up if the node type matches, no additional logic needed. I'd only use 1 if for some reason what is being checked is either very expensive or only possible within the context that you have there. – Berdir Nov 30 '16 at 19:44
  • 1
    Perfect answer. I think there is a missing back slash in the path for the access checking class in option 3 (srcAccess) – autopoietic Aug 10 '17 at 9:42

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.