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
There are routes where {node} is used but it is not upcasted, for example if the controller does not type hint it as NodeInterface.
Proposed resolution
Check with if ($node instanceof NodeInterface) instead of just if ($node}.
Remaining tasks
User interface changes
API changes
Comment | File | Size | Author |
---|---|---|---|
#6 | Issue-2397325-Use-node-instanceof-NodeInterface.patch | 1.3 KB | hass |
Comments
Comment #1
BerdirComment #2
hass CreditAttribution: hass commentedIs this something we always need to do? I have no idea why there could be a non-node object in $node. Maybe you could explain this to me a bit more. You know D8 internals better than me I think and I try to learn.
There may be one more place... See http://cgit.drupalcode.org/google_analytics/tree/google_analytics.module...
Comment #3
BerdirUpcasting depends on the type hints of the controller/form.
Things are only upcasted if the "target" has a matching interface, so if you define a route with {node}, if you use "NodeInterface $node" in your controller, then you get an upcasted node object, if you just have "$node", then you just get the ID.
If you have generic code that just accesses a route parameter based on the name, then you don't know what is the case.
Another thing, the correct way to access it would be \Drupal::routeMatch()->getParameter('node') (see https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Routing%2...), but I think the behavior is the same.
Comment #4
hass CreditAttribution: hass commentedI'm sorry, but I understand nothing what you wrote.
Comment #6
hass CreditAttribution: hass commented@Bedir: Can you take a look to this, please? Are you able to explain this change to me a bit, please?
Comment #7
BerdirThat's what I tried before :)
Let's say you have two routes (more or less made up):
/node/{node} => NodeController::view(NodeInterface $node)
and
/whatever/{node} => WhateverController::doSomething($node)
Then you access it with node/1 vs. whatever/1.
For node/1, the routing/upcasting system automatically loads node 1 and the parameter that is passed around from then on is a Node entity object. for whatever/1, this doesn't happen, and $request->attributes->get('node') or \Drupal::routeMatch()->getParameter('node') is just "1".
Now, if your code runs on such a page, it current fails. I think I've seen this on the confirmation page for reverting a revision, which has this route "/node/{node}/revisions/{node_revision}/revert". Because it never actually uses {node}, it is also not upcasted. So just create a revision of a node, and then try to revert that and you should see some errors.
Comment #8
hass CreditAttribution: hass commentedHow can content
->has('node')
= TRUE and than is not a node? Is there any other way to detect if we are running a node or not?Comment #9
BerdirBecause of what I just explained, twice? It is only upcasted when the controller requests an object with a type hint.
Why do you want another way, one is enough?
Comment #10
hass CreditAttribution: hass commentedI do not understand what you are talking about. That is the main problem. No idea what upcasting is. And I have no idea why this $node is not a node object. I'm asking for something simplier as I do not understand anything and I have no clue why $node is not a node. It is a WTF DX to me.
Comment #11
hass CreditAttribution: hass commentedI'm looking into the running request
This tells me we are running a node:
This gets these current node object:
And now you say this $node is not a node!? It looks like I have seriously not understood how Drupal works.
Comment #12
BerdirNo. That is *not* what it means.
It means you are on a route with a parameter {node}.
That is *just* a name. It could be anything. You can define a route '/whatever/{node}' and call it with whatever/banana. Technically, that is perfectly fine.
Now, there is a bit of magic, that for example for node/1, converts the route parameter {node} with the value '1' into a Node object. That magic is called upcasting (or sometimes parameter converting). That magic works by default*, under one condition: The controller for that route must type hint with NodeInterface, or at least EntityInterface, only then this happens.
* It works by default, but it is also configurable. You can load a {node} as a user, or a {whatever} as a node (sometimes, you want to load two different nodes in a route, but you can only have one {node}, this is why). See paramconverter_test.routing.yml for some fun examples.
Basically, this is the same as % and %node in D7, except that there are some more conditions now and it is more flexible/configurable.
And all of this is why you need to check if $node is actually a node entity. Because someone might have created a route where {node} is 'banana', just to mess with your head.
Comment #13
hass CreditAttribution: hass commentedHuh... I reviewed the D7 version, and
menu_get_object($type = 'node')
made sure it only loads node objects. So we are fine there. So now you say$node instanceof NodeInterface
makes sure we also only load nodes here. Ok so far. I wish I could grad a node object a lot easier from a request.Now I think I got at least your banana example. Let's come back to #7. I'm confused by your first sentence.
Does this mean my patch in #6 will not work or what are you trying to say? :-)
Comment #14
BerdirNo, I meant to I tried to explain it before. The patch is fine.
Comment #15
hass CreditAttribution: hass commentedComment #17
hass CreditAttribution: hass commentedThanks for your help.
Comment #21
hass CreditAttribution: hass commented