Support for Drupal 7 is ending on 5 January 2025—it’s time to migrate to Drupal 10! Learn about the many benefits of Drupal 10 and find migration tools in our resource center.
Problem/Motivation
I imagine it is a fairly common wish to create a new route or local task that is only applicable to a certain bundle or bundles.
At the moment that requires some custom access code.
Proposed resolution
And an _entity_bundles access checker.
Routes wishing to use this just get an extra requirement specifying the entity type and bundles that needs to match:
example.route:
path: foo/{example_entity_type}/{other_parameter}
requirements:
_entity_bundles: 'example_entity_type:example_bundle|other_example_bundle'
Remaining tasks
User interface changes
API changes
Only additions to routing.yml
Data model changes
None
Release notes snippet
An entity bundles access check has been added to make it easier for custom code to define bundle-specific routes.
Comment | File | Size | Author |
---|---|---|---|
#42 | 2858776-42.patch | 6.05 KB | mikran |
#42 | 2858776-interdiff-33-42.patch | 3.04 KB | mikran |
#33 | 2858776-33.patch | 5.93 KB | catch |
#33 | 2858776-interdiff-28-33.patch | 686 bytes | catch |
#28 | 2858776-28.patch | 5.93 KB | catch |
Comments
Comment #6
catchHere's a proof of concept.
New access checker - looks for an '_entity_bundle' route requirement.
The route requirement looks like:
Comment #7
catchComment #8
jonathanshawNice!
Nit: "_entity_bundle route requirement"
Shouldn't this be $route->getRequirement() not hasRequirement()?
Comment #9
catchUpdated for #8.
Comment #10
plachtypo:
$parameters
This is definitely missing test coverage ;)
Also, I was wondering whether we will ever need something like this to act on a route having the bundle name as parameter rather than an entity.
Comment #11
jonathanshawI just realised the patch doesn't seem to allow for multiple bundles.
_entity_bundle should accept an array?
Maybe that could be handled by a generic parameter filter:
Would be useful in all kinds of cases.
Comment #12
catchI initially tried an array, but route requirements can only be a string. We could do some custom serialization, but that gets ugly quickly, so seems better to keep this simple since a single bundle route is the most likely use-case here. Most modules either deal with one bundle, or rely on per-bundle configuration (forum, book) which can't be expressed in routing YAML and would require a custom access check anyway.
When the route has a bundle name, this could be handled with a route filter instead of an access check. The reason I had to do the access check here is because we only know the bundle once we get the entity, but if the bundle is in the request, it can be done in a filter during route selection. So this would probably need to be a different requirement with a different syntax.
Will add some tests.
Comment #13
catchNow with test coverage.
Comment #14
dawehnerThis looks like a nice little feature request for custom code!
I'm impressed how bad the documentation for
\Drupal\Core\Routing\Access\AccessInterface
actually is :)Reading this example, I'd suggest to do something like:
path: foo/{example_entity_type}/{other_parameter}
to make it more clear how this works.
Nitpick: It feels like the code would be a bit easier to read if this two nested ifs would be combined like
if ($entity instanceof EntityInterface && $entity->bundle !== $bundle)
Comment #15
catch#14 makes sense, updated for that.
Comment #16
plachLooks good to me, I have just a couple of minor remarks:
As @dawehner mentioned, it would be good for the example
path
to befoo/{example_entity_type}/{other_parameter}
, to make it clear that the requirement value must match it.We could use a data provider to avoid duplicating the test code.
Comment #17
catchAddressing #16.
Comment #18
catchArggh - with proper indentation.
Comment #19
plachLooks good!
Comment #20
alexpottIf this if is FALSE is returning a neutral result correct? If the route has the _entity_bundle requirement and there is no entity parameter then I think this should be forbidden. Not sure though.
I think there is some could somewhere that changes an entity's bundle - at least in contrib - do we need to care about cacheability here?
We can provide a reason here.
Comment #21
catch#1. I'm also not sure. The most likely case that would happen would be a mis-use of this feature somehow, say a c&p error. I thought about throwing an exception there too. Don't have a strong opinion.
#2. It does exist, doesn't hurt to add it. https://www.drupal.org/project/convert_bundles
#3. Yes we should add this.
So... could do with a third opinion on #1.
Comment #22
jonathanshawRe #20.1
If you have this neutral, you're saying "allow this route for this bundle of this entity type, but block it for other bundles of this entity type, but allow it for other entity types" which is a semantic I can't see anyone expecting.
Comment #23
Berdir20.1 Route access checks are AND-combined, so there is very little difference between neutral and forbidden. All access checks must return allowed or access is denied.
Comment #24
plach@catch:
Given the comments above, maybe it would make more sense to reverse the logic and always return
AccessResult::forbidden()
unless$entity instanceof EntityInterface && $entity->bundle() === $bundle
?Comment #25
plachI got back to #11 with @catch since in our project we will likely need also multiple bundles. I suggested the following custom serialization which @catch didn't dislike:
_entity_bundle: node:article|page|news
. This seems a good combination of readability and ease of implementation to me.One reason why it would make sense to support this custom format is that, if support for multiple requirements is ever introduced, it would still require to prefix each requirement with the same entity type:
Additionally, if we adopt this custom format, we would be able to do this:
if the route supports multiple entity types at once.
Comment #26
catchYes I really like the format in #25, it's fairly self-explanatory and doesn't require duplicating the entity type which was my main objection to trying to support multiples.
Here's an updated patch addressing #20, #24, and #25. Excuse the double interdiff I missed a bit..
Comment #27
plachCan we keep these on the same line, otherwise it might be confusing.
Per #23 shouldn't these be
::allowed()
?What about adding another equivalent test case having
article
as bundle?Comment #28
catchAddressing #27.
Comment #29
plachLooks good, thanks, back to RTBC!
It would be good to provide a CR and an IS update.
Comment #30
catchComment #31
catchComment #32
jonathanshawFixed IS nit. CR looks good.
Comment #33
catchFailed to update the actual service definition.
Comment #34
catchAlso there might be a core use-case for this.
If you add a view as a tab on an entity type, then even if you add validation by the entity bundle and restrict it, the tab shows up for all bundles (leading to a 404). This is because argument validation isn't access.
So we could add a views access handler that specifies the bundles of the entity type it appears on maybe.
Comment #35
plachI think #34 is follow-up material, back to RTBC.
Comment #36
catchAdded the follow-up.
Comment #37
johnwebdev CreditAttribution: johnwebdev commentedThis does not really explain the intent of this route requirement. Given it's name, you could most likely guess it, but we could probably be more explicit.
Nit: Missing a "."
Nice, to make it complete we could add one more, testing multiple bundles that should be forbidden as well.
Comment #38
johnwebdev CreditAttribution: johnwebdev commentedComment #39
johnwebdev CreditAttribution: johnwebdev commentedMissing visibility on the method.
Comment #40
tstoecklerPer #23 shouldn't this be allowed and neutral instead of allowed and forbidden?
Comment #42
mikran CreditAttribution: mikran at Mediamaisteri Oy commentedI've addressed comments 37,39 and 40. On top of that I removed two unused uses that were added by this patch.
Comment #43
jonathanshawIt's not clear that @tsoeckler in #40 is right; @alexpott in #20 advocated forbidden not neutral, @berdir in #23 said it didn't matter. But I think given #23 perhaps it's fine as is.
Comment #44
alexpottCommitted and pushed 5def5fc1d2 to 9.0.x and 4b5f1ac075 to 8.9.x. Thanks!
Comment #47
alexpottComment #48
Kasey_MK CreditAttribution: Kasey_MK commentedThanks for this!
I was a bit disappointed to discover that this wouldn't let me add node bundle requirements to a Views-created menu tab, e.g.,
It did, however, let me make a custom route with the correct bundle requirements. I set the view (a page display) to "No menu" and then created my own route for it.
my_module.routing.yml:
my_module.links.task.yml:
Now I have a "Subscribers" tab on nodes of two different bundle types but no others.
In order to then get the node tabs to show up on my view page:
Comment #50
claudiu.cristeaWe are deprecating this route requirement in #3155568: Filter by bundle in EntityConverter route param converter (see comment #11)