Problem/Motivation
When customising the toUrl method of an entity, such as with bundle classes, its possible to return non-routable Url objects.
\Drupal\path\Plugin\Field\FieldType\PathFieldItemList::computeValue assumes that all Urls returned by \Drupal\Core\Entity\EntityInterface::toUrl are routable.
When this code path executes, with a non-routable Url, the following exception is thrown:
UnexpectedValueException: Unrouted URIs do not have internal representations. in Drupal\Core\Url->getInternalPath() (line 798 of core/lib/Drupal/Core/Url.php).
Drupal\path\Plugin\Field\FieldType\PathFieldItemList->computeValue() (Line: 33)
....
Path.module should simply handle non-routable Urls.
Original report
I'm using a bundle class to override the toUrl() method on a particular node type. In particular, there's a link field that, if populated, will be used by the toUrl() method. The reason is that we want to allow cards to link to internal or external links (or no link whatsoever) as elegantly as possible. The code looks like this:
public function toUrl($rel = 'canonical', array $options = []) {
if ($rel === 'canonical') {
if (!empty($this->field_link->uri)) {
return Url::fromUri($this->field_link->uri);
}
else {
return Url::fromRoute('<nolink>');
}
}
return parent::toUrl($rel, $options);
}
I can successfully create such a node and the behavior of toUrl is as hoped.
Unfortunately, editing the node is not so smooth. The path module throws an erro:
UnexpectedValueException: Unrouted URIs do not have internal representations. in Drupal\Core\Url->getInternalPath() (line 798 of core/lib/Drupal/Core/Url.php).
Drupal\path\Plugin\Field\FieldType\PathFieldItemList->computeValue() (Line: 33)
....
When I fix that by hacking core a bit then I get to the next exception:
UnexpectedValueException: Unrouted URIs do not have internal representations. in Drupal\Core\Url->getInternalPath() (line 798 of core/lib/Drupal/Core/Url.php).
Drupal\path\Plugin\Field\FieldWidget\PathWidget->formElement(Object, 0, Array, Array, Object) (Line: 19)
....
When I fix that then everything is grand.
These exceptions happen even if the url alias element is not on the node form display for this node type.
Steps to reproduce
Return a non-routable Url from a entity::toUrl, such as with a bundle class.
Proposed resolution
path.module needs to check that $entity->toUrl()->isRouted() in addition to its existing checks for !$entity->isNew() in a couple places.
Remaining tasks
Tips for manual testing
Apply the test-only patch.
Enable entity_test, path, and path_entity_test_external. (To enable test modules, add $settings['extension_discovery_scan_tests'] = TRUE; to your settings.php.)
Then go to /entity_test_external/add and create and edit these entities. See the errors.
Then apply the fix and see that creating and editing these entities works. And everything should still work for nodes and media, etc.
User interface changes
N/A
API changes
TBD
Data model changes
N/A
Release notes snippet
TBD
| Comment | File | Size | Author |
|---|---|---|---|
| #55 | path_getInternalPath_routed_3342398-55.patch | 12.71 KB | mighty_webber |
| #52 | 3342398-52-for-11-2.patch | 14.45 KB | mlncn |
| #49 | 3342398-nr-bot.txt | 90 bytes | needs-review-queue-bot |
| #47 | Screenshot 2024-09-26 at 6.01.26 PM.png | 88.62 KB | samitk |
| #37 | 3342398-37.patch | 14.27 KB | quietone |
Issue fork drupal-3342398
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
Comment #2
danflanagan8Here's a patch. I'm adding the Needs tests tag.
Comment #3
smustgrave commented#2 didn't seem to break anything and changes seem innocent enough. Think we good to start the tests?
Comment #4
danflanagan8It took all day, but I have tests! I also improved upon the patch in #2 a bit.
I started with a Unit test for PathFieldItemList, which was brutal. Then I made a functional test that leverages the entity_test_external entity type that was added as part of tests for #2914785: Entities with external urls as a uri relationship can not be deleted when menu_link_content is installed. The behavior of the entity_test_external entity type is extremely similar to what my snippet in the IS does:
So I didn't have to make a new entity class, but I did have to add a few things to the annotation so that I could get add and edit forms.
It turns out the functional test exposes the same bug exposed by the Unit test. So I'm not sure the Unit test provides a ton of value. It's there if we want it, but I'd be fine if we were to decide to remove it from the patch.
Comment #5
danflanagan8Comment #6
danflanagan8The Functional test exposes several bugs in series, but it can obviously only fail once at a time. So here I'll take you through how each hunk of the fix causes the test to get farther before failing.
If I run the tests with no fix, I get this failure error when trying to create the entity:
The hunk that fixes this is:
The next error I get is this when I try to edit the entity:
The fix for that is this:
Theeeeen, the test fails when I try to set a path for the entity and update the entity:
That gets fixed by the changes to `Drupal\path\Plugin\Field\FieldType\PathItem`.
After that the test would pass fine except that I thought it would be nice to add a validation error if a path is being set on an entity that isn't routed.
Without that code, there aren't any exceptions or errors, but the value of the url alias field is cleared out without any notice to the user. I think this validation error is better than that. This validation does not work if the entity is new, however. If the url alias field is populated for new unrouted entity, the content of that field simply get cleared out during
PathItem::postSave(). Maybe we should add a message if that happens?Comment #7
danflanagan8Ignore patches in #4. All the comments in #4 and #6 are still valid. Don't ignore those. :)
Comment #8
danflanagan8Oy! The violations only increase when the fix is in place. So this new fail test backs out the change to the baseline. Apologies for the mess.
Comment #10
danflanagan8Comment #12
danflanagan8Gosh, I had the wrong namespace for the new Unit test.
I need to change:
namespace Drupal\Tests\path\Unit;to
namespace Drupal\Tests\path\Unit\Field;Here are new fail and pass patches that only run the new unit test.
Comment #13
danflanagan8I'm going to stop trying to be cute. Let's just make correct fail and pass patches and post them. Deep breath.
Let's hope 13 is my lucky number :)
Comment #15
danflanagan8Comment #16
danflanagan8I ran across another exception when I deleted an entity like this:
So I need to add to the test such that we delete one of the external entities. And I need to fix it, of course.
Comment #17
danflanagan8Here's a patch that address what I mention in #16, both the test coverage and the fix.
Comment #18
danflanagan8Here's a patch that should apply for D9.
Comment #19
smustgrave commentedTest coverage seems good and the change makes sense.
Comment #20
danflanagan8Thanks for the review, @smustgrave.
There's one piece of code that I want to call out as not covered by the automated testing. It's this
It's the "if the url has become unrouted" part of that conditional. It would be possible to update the coverage to cover that, but it would require using a different entity than
entity_test_external. That entity always has an unrouted url, so it can't be used to test the case where an entity has a routed url and then gets updated such that its url is unrouted. That's actually something that happens with the bundle class I describe in the IS.I liked the idea of re-using the class that was already in the codebase for writing these tests. But it's not quite complete coverage. But I think everything else is covered in want ends up being a pretty simple functional test.
I'm also adding a related issue which is the issue for which the entity_test_external class was added.
Comment #21
smustgrave commentedWill see what the committer thinks. Also like reusing what's there if possible. But if we do need coverage for that could we still reuse that entity and make a new one for the one scenario? Seems overkill just speaking out loud.
Comment #23
needs-review-queue-bot commentedThe Needs Review Queue Bot tested this issue. It fails the Drupal core commit checks. Therefore, this issue status is now "Needs work".
This does not mean that the patch needs to be re-rolled or the MR rebased. Read the Issue Summary, the issue tags and the latest discussion here to determine what needs to be done.
Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.
Comment #25
bharath-kondeti commentedRebased the patch for 11.x
Comment #26
danflanagan8There's something clearly wrong with the patch in #25. It's half the size of the most recent patch I posted. Let's ignore it.
I'm hoping that the review bot kicked this back because the most recent patch I uploaded was for 9.5.x. I would not expect that one to work against 10.x or 11.x. I'm going to re-upload the patch from #17, which is really the one that @smustgrave RTBC'd. Hopefully it still works.
Comment #27
danflanagan8Yes, looks like I was correct. I'm going to reset this to RTBC since it's the same patch that @smustgrave RTBC'd in #21. It just got kicked back because I uploaded a D9.5 patch in #18 and that's the one that the review-queue bot was checking.
Comment #28
catchJust a couple of nits but one I'm not sure how to fix on commit.
Should this be Path form UI as in the class phpdoc? LOoks like it could be copypasta.
'place use' looks like an editing typo but I'm not sure what it's actually describing here.
Comment #29
danflanagan8Hi @catch,
I'm terribly sorry about those broken comments. Regarding #28.2, not even I could figure out what I was trying to say! I wonder if I deleted a whole line or two on accident? The point of the comment was to explain why I was setting a couple things on the container. So I kind of started from scratch on that comment and now I think it makes sense.
Comment #30
smustgrave commentedSeems points in 28 have been addressed
Comment #32
danflanagan8That was an unrelated fail being tracked here: #3376563: Random test fail in Drupal\Tests\Component\Utility\RandomTest::testRandomMachineNamesUniqueness
Back to RTBC.
Comment #34
danflanagan8Same fail as described in #32. Back to RTBC.
Comment #35
quietone commentedI'm triaging RTBC issues. I re-read the IS and the comments. I didn't find any unanswered questions or other work to do.
I read the Issue Summary and the comments. Of special note is the details on what is not being tested in #20.
I skimmed that patch and this error message caught my eye.
Since this will show in the UI I think it should be easier for an admin to understand and to fix. And it should not continue to use 'unrouted' which is a misspelled word in the dictionary. From the IS I gather that this is an uncommon case. Therefore, how about we at least avoid the spelling error and make a followup, tagged usability, to improve the message. Unless someone has a better idea how about, "An entity without a route cannot have a path."?
Actually, I have a moment and I will update the patch. Tagging for a follow up.
Comment #37
quietone commentedCompletely forgot about the test.
Comment #38
smustgrave commentedOpened https://www.drupal.org/project/drupal/issues/3384376 as a follow up
Comment #40
dpiThanks for the this, just what I needed.
Comment #44
dpiFixed conflicts for 10.2 and 11.x and created a MR for 11.x.
No changes other than merge fixes, deleting the since deleted PHPStan neon file.
Comment #45
dpiSmall IS rewrite.
Comment #47
samitk commentedHi @dpi,
I observe a issue, that post enabling
Path entity_test_externalmodule, TheURL aliasfield value is not getting saved while saving theentity_test_externalentity.attaching the Screenshot.
Thanks
Samit K.
Comment #48
mlncn commented@samit My understanding of the test is that the entity should not be able to be saved, and so the alias does not get set, because it does not have a route (the test checks for the error message stating that).
If this still passes tests it should be back to RTBC.
Comment #49
needs-review-queue-bot commentedThe Needs Review Queue Bot tested this issue. It no longer applies to Drupal core. Therefore, this issue status is now "Needs work".
This does not mean that the patch necessarily needs to be re-rolled or the MR rebased. Read the Issue Summary, the issue tags and the latest discussion here to determine what needs to be done.
Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.
Comment #50
mlncn commentedComment #51
mlncn commentedRe-roll complete; the phpstan error is a 500 Internal Server Error so probably not due to our code here.
Comment #52
mlncn commentedHere is a version that applies to Drupal 11.1 for anyone who needs that.
Sorry, is there a convention for patches we want the bot to ignore?
Comment #53
smustgrave commentedLeft some comments on the MR.
Comment #54
mighty_webberPatch for Drupal 10.4.9
Comment #55
mighty_webberFix for Patch for Drupal 10.4.9
Comment #57
dpiPushed a merge into main (11.3)