7

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?

2
  • You can restrict it in MyForm . You can check the content type there Nov 30, 2016 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, 2016 at 8:07

2 Answers 2

18

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 these 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.

2
  • 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, 2016 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) Aug 10, 2017 at 9:42
1

Huge apologies for posting an answer years later, but I just wanted to say that this is possible in Drupal core now (since version 9.2.0.)

You can define a route like this:

example.route:
  path: foo/{example}
  options:
    parameters:
      example:
        type: entity:node
        bundle:
          - article
          - news

As per: https://www.drupal.org/node/3155569

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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