Problem/Motivation

I want to be able to tell the entity param converter (EntityConverter) which bundles to be converted on route where a param is an entity ID. For instance, I want to be able to create such a route definition:

some_route:
  path: /node/{node}/foo
  defaults:
    _controller: ...
  ...
  options:
    parameters:
      node:
        type: entiity:node
        bundle:
          - blog
          - article   

With this definition I would expect that passing a node ID of a node of type 'blog' or 'article' to get the {node} param converted into a node entity but when I pass a node ID of a 'page' node to get a 404, as this route business purpose is to deal only with those node types and the route should "not exist" for other kind of nodes.

Proposed resolution

I've implemented this in our project by swapping the class of the paramconverter.entity service but I see here some value that can be added to Drupal core.

Remaining tasks

None.

User interface changes

None.

API changes

Route parameters of type entity:* accept a new bundle definition property of type array.

Data model changes

None.

Release notes snippet

In order to limit the route entity parameters to certain bundles, a 'bundle' property, of type sequence, may be added to the parameter's definition.

Issue fork drupal-3155568

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

claudiu.cristea created an issue. See original summary.

claudiu.cristea’s picture

Status: Active » Needs review
Issue tags: -Needs change record
StatusFileSize
new4.4 KB

Patch. Added also a change record.

claudiu.cristea’s picture

Issue summary: View changes
claudiu.cristea’s picture

StatusFileSize
new2.62 KB
new4.59 KB

Renamed 'bundles' to 'bundle'. Improved docs.

claudiu.cristea’s picture

Issue summary: View changes
claudiu.cristea’s picture

Title: Allow to refine by bundle in EntityConverter » Filter by bundle in EntityConverter route param converter

Title change.

johnwebdev’s picture

What about the _entity_bundles requirement? See EntityBundleAccessCheck

claudiu.cristea’s picture

@johnwebdev, that is somehow similar just that is doing a different thing. If you pass a wrong bundle to a route that requires _entity_bundles, you'll receive a 403 - 'access denied'.

But there are circumstances when you really want a 404 - 'page not found'. Let's take this hypothetical case:

  • 3 node types: 'vegetable', 'fruits', 'blog_entry'
  • A route whose controller shows all sales for a given product (either vegetable, or fruit): /node/{node}/sales.

This route makes sense only for nodes of type 'vegetable' and 'fruits'. If a 'blog_entry' node is passed as parameter, I don't want a 403, which would mean "Hey the page exists but you cannot access it". Instead I want a 404 because that route makes no sense when a 'blog_entry' node is passed from a site's business logic perspective.

Version: 9.1.x-dev » 9.2.x-dev

Drupal 9.1.0-alpha1 will be released the week of October 19, 2020, which means new developments and disruptive changes should now be targeted for the 9.2.x-dev branch. For more information see the Drupal 9 minor version schedule and the Allowed changes during the Drupal 9 release cycle.

dimilias’s picture

Status: Needs review » Reviewed & tested by the community

Following the #8 I should say that indeed, there are cases where for different bundles you have distinct functionalities, e.g. Wanting to send a newsletter for the new blogpost content but not for a new page content. Returning 403 for the mail send page is not the best candidate to return in this case as it is really not a forbidden, it is not a functionality, non-existent.
This worked like a charm for me. Thanks.

catch’s picture

Status: Reviewed & tested by the community » Needs work

So #2858776: Make defining bundle-specific routes easier is the issue which added the _entity_bundles functionality, which I worked on a bit.

I think if we'd thought of this approach, we'd have done this instead. I don't think we need both - so IMO we should deprecate _entity_bundles as part of this issue, and use this approach instead - less code and doesn't require the piped format to list the bundles. Needs work to add the deprecation though.

claudiu.cristea’s picture

Status: Needs work » Needs review

Deprecated the _entity_bundles route requirement. Updated the change notice.

claudiu.cristea’s picture

jonathanshaw’s picture

#2858776: Make defining bundle-specific routes easier facilitates some things that this does not allow e.g node/vegetable/manufacture or node/vegetable/inventory. Just saying.

claudiu.cristea’s picture

@jonathanshaw, not sure I get your use-case. Could you elaborate?

jonathanshaw’s picture

Sorry, I was not thinking properly.

claudiu.cristea’s picture

Ready for RTBC again.

Added deprecation required in #11. Note that route access checkers cannot be deprecated following the class and service deprecation policy because such services are instantiated regardless they are not used by any route. See @Berdir's merge request comment. As per discussion I had on Slack with @catch and @Berdir, we agreed to deprecate access checker service in the same way as LayoutSectionAccessCheck has been deprecated in Drupal 8.

However, as mentioned, we've discovered in this issue that all access checker services are instantiated regardless even some might not be used by any route. I've opened #3183036: Don't instantiate access checkers not used by any route as follow-up to fix that.

jonathanshaw’s picture

1.

 *     parameters:
 *       example:
 *         type: entity:node
 *         bundle:
 *           - article
 *           - news

There's an inconsistency in the format here. I would have expected either
A:

 *     parameters:
 *       example:
 *         type: entity
 *         entity_type: node
 *         entity_bundle:
 *           - article
 *           - news

or

B:

 *     parameters:
 *       example:
 *         type: entity:node:article

What we have in this patch is kind of hybrid. That seems OK to me though. (A) and the patch handle arrays better than (B), but (A) is not backwards compatible.

2. Deprecated in drupal:9.2.0 and is removed from drupal:10.0.0 Isn't that "will be removed before" by convention?

3.

 *     parameters:
 *       example:
 *         type: entity:node
 *         bundle:
 *           - article
 *           - news

The singular bundle makes it sound like bundle: article is OK. Maybe this should this be bundles in plural. And if bundle: article works, we should ideally have test coverage for it and mention in documentation.

claudiu.cristea’s picture

@jonathanshaw,

#19.1: That is out-of-scope and, anyway, that would be a change with very high disruptive potential. Moreover, #19.1.B is not allowing multiple bundles.

#19.2: I've strictly followed the Drupal core deprecation policy regarding classes.

#19.3: Well this was on my mind from the first time, but as this is an "machine readable" key we are using in many places singular, even we are referring an array. Still not strong feeling. The string fallback (when there's only one item) was also an my mind but I really don't like these kind of "magical tricks". I prefer to be strict, pass it as array or leave it.

jonathanshaw’s picture

Status: Needs review » Reviewed & tested by the community

Fair enough.

  • catch committed f6bde54 on 9.2.x
    Issue #3155568 by claudiu.cristea, jonathanshaw, idimopoulos, catch:...
catch’s picture

Status: Reviewed & tested by the community » Fixed

Committed f6bde54 and pushed to 9.2.x. Thanks!

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.

extexan’s picture

I'm having trouble finding the right syntax to do what we need to do re: our "add content" routes.

Some example URLs in our client's site are:

/sales/customers            <- list of customers
/sales/customers/123/edit   <- edit customer 123
/sales/claims               <- list of customer claims
/sales/claims/234/edit      <- edit claim 234

We need to use the "edit" examples above instead of /node/123/edit in order for our menu highlighting to work. That is, when you are editing customer 123, the "Sales" menu is expanded, and the "Customers" submenu item is in bold.

The overrides we've added for edit and delete routes are working fine. But we also want to do the same for "add" routes, like this:

/sales/customers/add
/sales/claims/add

That's what I can't get working. It was working fine in D8, but not in D9. The biggest problem, it seems, is that we have no "variable" part of our route. Meaning {node_type} is not in the URL. Some of the things I tried before I found this issue worked, but made our URLs come out like this:

/sales/customers/add?node_type=customer
/sales/claims/add?node_type=claim

We don't want that in our URLs. I've also made it work by defining the paths like this:

/sales/claims/add/{node_type}

But that makes the actual URL look like this:

/sales/claims/add/claim

...which we also don't want. Using the node.add configuration from the Drupal core node.routing.yml as a starting point, adding what I've learned from this thread, I've tried everything I can think of to get this to work. This config...

example_reroute.claim_add:
  path: '/sales/claims/add'
  defaults:
    _entity_form: 'node.default'
    _title: 'Create Customer Claim'
  requirements:
    _node_add_access: 'node:claim'
  options:
    _node_operation_route: TRUE
    parameters:
      node_type:
        type: entity:node
        bundle:
          - claim
        with_config_overrides: TRUE

...gives a "Missing bundle for entity type node". I can "fix" that error by adding back something in our original config, which is:

example_reroute.claim_add:
  path: '/sales/claims/add'
  defaults:
    _entity_form: 'node.default'
    _title: 'Create Customer Claim'
    node_type: 'claim'
  requirements:
    _node_add_access: 'node:claim'
  options:
    _node_operation_route: TRUE
    parameters:
      node_type:
        type: entity:node
        bundle:
          - claim
        with_config_overrides: TRUE

Note the addition of "node_type" under "defaults". This gives a "Page not found" response.

I think the issue stems from the fact that this new "bundle" option is under the "node_type" entry of "parameters". We don't have (and don't want) a "node_type" parameter in our URL, so specifying those parameters in our routing config is not really doing anything.

Is there a way to get these URLs to work as we intended?

jonathanshaw’s picture

@ExTexan please don't use closed issues for requesting support. The best way to get help with this is probaly Drupal Answers.

extexan’s picture

@Jonathan,

Sorry about that. I figured this would be the appropriate place for my question for two reasons... 1) Previous comments have examples of how to use this feature (examples that didn't work for me), which would help anyone answering my question get up-to-speed on the issue more quickly; and 2) The devs who implemented this feature have commented here, so by default, they are following this issue; I thought they would be my best source for an answer.

But, no worries, I'll create a new support issue.

dpi’s picture

Created a revision version of this issue at #3273229: Filter by bundle in EntityRevisionParamConverter route param converter. Ideally this issue makes it in for 9.4/10, otherwise I'd prefer EntityBundleAccessCheck deprecated by this issue to be un-deprecated so we have a solution for revision parameters.

donquixote’s picture

The solution implemented here does not work if we have multiple routes with same path, but for different bundles.

The ParamConverter is only called _after_ the route was already picked in Router::matchCollection().
So it is too late at that point.

donquixote’s picture

I found that even the old mechanism would not have worked.
So nothing was made worse in this issue.
Sorry for the noise.

The Router::matchCollection() does not call the access checks.
It only calls $route->getCondition() which seems to provide some additional regex checks, but this does not really help to distinguish bundles.

So this is a dead end: We should not have multiple routes with the same path.