Problem/Motivation
Part of #2339219: [meta] Finalize URL generation API (naming, docs, deprecation). url()
needs to be deprecated and removed from Drupal 8, because it circumvents the routing system for internal URLs.
Proposed resolution
Convert as many straightforward l()
calls as possible.
- For entities use
$entity->urlInfo()
to get an url object to this entity. Then use\Drupal::linkGenerator()->generateFromUrl($text, $url);
to generate the link - For internal links,
\Drupal::l()
(which takes a route name) is usually the correct replacement in procedural code. - Uses of
l()
for external URLs won't be converted until #2343759: Provide an API function to replace url()/l() for external urls is completed.
Remaining tasks
The patch converts all but 49 uses of l()
to the appropriate replacements. Many of the remaining conversions are blocked by one of these issues:
#2345725: Query parameters are not decoded the same as the path portion of a URL
#2344487: Provide special route names for <current> and <none>
#2343759: Provide an API function to replace url()/l() for external urls
In followup issue #2343661: Rename l() to _l() and url() to _url(), and document replacements, we will rename l()
to _l()
to make the BC break explicit, and mark it deprecated. After that, the remaining uses of _l()
will be removed before 8.0.0, in #2343669: Remove _l() and _url().
User interface changes
None.
API changes
None yet (in other issues).
Comment | File | Size | Author |
---|---|---|---|
#61 | l-2343651-61.patch | 59.65 KB | Wim Leers |
#58 | interdiff.txt | 822 bytes | tim.plunkett |
#58 | 2343651-l-58.patch | 59.64 KB | tim.plunkett |
#54 | interdiff-l-54.txt | 5.98 KB | xjm |
#54 | 2343651-l-54.patch | 59.1 KB | xjm |
Comments
Comment #1
xjmComment #2
xjmComment #3
xjmComment #4
dawehnerAdapted the info to include the proper way for entities. Here is a patch which JUST converts 2 instances given an example for both.
Comment #5
dinarcon CreditAttribution: dinarcon commentedSome additional changes.
Shall links to 'javascript:void()' use Drupal::l?
Comment #7
dinarcon CreditAttribution: dinarcon commentedComment #8
dinarcon CreditAttribution: dinarcon commentedComment #9
dawehnerYou could use $user->urlInfo('edit-form') again (see initial patch as example
Let's keep them for now.
Comment #11
dawehnerWorking on also making it possible to get a url object from views.
Comment #12
dawehnerSome work.
Comment #14
Wim LeersPushing this one further.
Comment #15
Wim Leers(While debugging the test failure in
Drupal\user\Tests\UserAdminTest
.)Apparently,
UrlGenerator
is broken, and its test coverage is incomplete, because:prints:
l()
callsurl()
which callsUrlGenerator::generateFromPath()
, which usesUrlHelper::buildQuery()
to build the query string.\Drupal::l()
callsUrlGenerate::generateFromRoute()
, which callsgetInternalPathFromRoute()
, which calls\Symfony\Component\Routing\Generator\UrlGenerator::doGenerate()
, which … of course doesn't callUrlHelper::buildQuery()
and therefore explains the difference.That's a pretty clear regression. But the only ways to fix it are: 1) fix upstream (no time to do that before beta), 2) don't use Symfony's
UrlGenerator::doGenerate()
, 3) allow the regression for now.Comment #16
effulgentsia CreditAttribution: effulgentsia commentedRe #15, see #2340251-31: Remove most remaining url() calls
Comment #17
Crell CreditAttribution: Crell commentedWim, can you explain why those are functionally different? Symfony seems to work with links, so is it an actual regression or just "different"?
Comment #18
Wim LeersI have to call it a day now, will continue tomorrow. Didn't get much done, sadly, had other obligations, and debugging these is pretty painful.
Progress report:
Drupal\toolbar\Tests\ToolbarHookToolbarTest
failure.Drupal\user\Tests\UserAdminTest
root cause explained in #15. Indirectly caused byDrupal\user\Plugin\views\field\LinkEdit
still rendering usingl()
, converting that to\Drupal::l()
will fix the fail, but the way those links are rendered is … non-trivial … so I will need to step through it with a debugger to find wherel()
is being called :)Drupal\taxonomy\Tests\TermTest
fails because inTermForm::save()
,$term->id()
is NULL, i.e. the term didn't get created at all.Comment #19
Wim LeersCrell: because this was done intentionally, for legibility:
Comment #21
Wim LeersHave some unexpected time to push this forward further.
Comment #22
Upchuk CreditAttribution: Upchuk commentedI did some more conversions. Would have done more but there were some external links + some more complex cases I'd be more comfortable if you took a look at the Wim.
What about the l() with
current_path()
in them? Can we use the request object to get the current route?Let me know what else I can do to help.
Comment #23
Wim LeersThis should bring it down to 1 fail and 1 exception.
(Cross-posted with Upchuk, so applied my interdiff to #22, the numbers above apply to #18.)
Comment #24
dawehner@Wim Leers
Mh actually the UrlGenerator already takes into account that. See
UrlGenerator::$decodedChars
. Will be worth to debug.20:2 failures, this is nothing.
Let me work on the converting some of the view ones.
Also haven't done the views bit we talked about because this is kind of a mindfuck.
Comment #25
dawehnerMEH! :(
Comment #26
dawehnerStarted with #23 and merged mine in.
Comment #27
dawehnerAdding a related issue.
Comment #31
Wim LeersI'll continue with this one.
Comment #32
Wim Leers#24: no,
UrlGenerator::decodedChars
is used for the path segment only! :(Comment #33
Wim LeersThis should fix at least 48 of the 107 fails.
Comment #34
Wim LeersThis should fix another 35 fails, bringing it down to 24 hopefully.
All the test failures in Shortcut module were related to a single wrong invocation of
\Drupal::l()
(which is an alias forLinkGenerator::generate()
): it was being called with aUrl
object where astring $route_name
was expected. Because PHP doesn't have astring
typehint and we don't do explicit checking, this is what happened.I haven't added a manual type check plus exception yet, because I know that's a bit controversial, but we might want to do that. It took me 25 minutes to find this. With such an exception, it would only have taken a second.
Comment #37
Wim LeersRemoved 3 more
l()
invocations (one was in dead code). Number of failed tests should remain the same.There are only 3 remaining
l()
invocations to be converted:Link::preRenderLink()
. That usesl()
, but to fix that, we have to fix all code that uses#type => link
. Grepping for#href
will reveal those.ViewListBuilder::getDisplayPaths()
andViewUI::renderPreview()
, which likely needs deep Views knowledge to be fixedAfter that, I think we have no remaining
l()
calls that can be converted, they all:current_path()
and hence need #2344487: Provide special route names for <current> and <none>, orThese represent 45
l()
invocations. These would have to use the renamed_l()
function IMO, as per #2343661: Rename l() to _l() and url() to _url(), and document replacements.Comment #38
Wim Leersd.o--
Comment #40
divesh.kumar CreditAttribution: divesh.kumar commentedI tested it and worked fine however its getting failed due to other changes.
Comment #41
tim.plunkettWorking on this.
See also #2277103: Switch Drupal::l() and LinkGenerator to expect a Url object
Comment #42
tim.plunkettOpened #2345725: Query parameters are not decoded the same as the path portion of a URL and linked in this patch, since we can't convert anything using / in query parameters.
Comment #44
xjmA few DX questions/observations:
Out of scope here, but I wanted to bring it up: So this is the right change for the API we have. Replacing
'node/1'
with'entity.node.canonical', ['node' => 1]
is definitely a bummer, though. It's the sort of little thing that incites mindless rage at Drupal 8.How would #2277103: Switch Drupal::l() and LinkGenerator to expect a Url object affect code like this? (Trying to get some insight on whether we should try for or give up on that beta deadline API change.)
Q: What on earth is this used for nowadays? A: Two blocks we still need to convert to views. #2020387: Convert "Active forum topics" block to a View and we don't even seem to have an issue for the statistics block that I could find.
Nice.
Why does this use
\Drupal::linkGenerator()->generateFromUrl()
while the example in the Views test uses\Drupal::l('Node ' . 1, 'entity.node.canonical', ['node' => 1])
? What's the reason to use the route name vs. the entity URL object?For reference: Entity::urlInfo()
Another case where I'm unclear as to why the route vs. entity URL object is used.
So I guess the only thing I'm unclear of is for entities, when it's best practice to use the route name vs. the entity URL method, and why half the patch is one way and half the other. If it were always route names with an
$entity->id()
, the pattern would be more learnable.Comment #45
tim.plunkettHere's the fix for that fail.
I think this is enough for one patch, we'll need #2344487: Provide special route names for <current> and <none> and dedicated follow-ups for more complex conversions.
#44.1 This is really only common in tests. Note that it doesn't have a fully loaded node.
4,5 The difference is some places don't have a fully loaded entity. #2277103: Switch Drupal::l() and LinkGenerator to expect a Url object would definitely help the ones that do.
Comment #46
xjm@tim.plunkett discussed 4 and 5 in IRC, and contemplated the API addition of an
Entity::l()
method (or something with a better name) that would return the 90% case of a link to the entity with the entity label as the link text.I've left additional comments on #2277103: Switch Drupal::l() and LinkGenerator to expect a Url object about that change WRT the above.
I'm comfortable with this going in as it stands, since the meh DX is not introduced by this patch, just surfaced, and
l()
to entities generally are only a small subset of all thel()
calls that we need to worry about. Going to do a local review of #45 and then update the summary here.Comment #47
xjmPer discussion with tim.plunkett, this should use the same entity link generation pattern as above, not the static route name. I asked Tim why using the route name was "wrong" and he said:
I'll reroll with that change.
Also referencing related issue for entity URI templates.
Comment #48
Wim LeersWoah. That implies that we have to fix this all over core, in theory.
It makes sense, but it's a sad realization. 2 levels of abstraction to get an entity path: EntityInterface::urlInfo() to get the route name, the route name to get the path.
Comment #49
tim.plunkettHence, #2281645: Make entity annotations use link templates instead of route names. But that's certainly off-topic
Comment #50
xjmAttached addresses #47. The log entry has no test coverage so I tested manually and found #2345779: Fix double-escaping due to Twig autoescape in dblog event "operations" (so apparently there's no test coverage for dblog "operations" links whatsoever). However, the double-escaped link is the same in HEAD as the patch, so out of scope. :)
Comment #51
xjmThese (and elsewhere) could use
$this->getLinkGenerator()
instead.Comment #52
xjmComment #53
xjmComment #54
xjmAttached uses the link generator trait in page and form controllers (as per #51).
Comment #55
xjmReviewed locally with
git diff --color-words
; noticed a couple things:This was a misuse of
l()
to begin with; however, shouldn't the external URL go through whatever we come up with for https://www.drupal.org/node/2343759 instead, or, for now,UrlGenerator::generateFromPath()
instead?Not sure why we're changing this test to use the front page instead of two particular nodes?
Out of scope, but have we considered converting this to D8 modals? :) Or maybe there's too much content for a modal to be usable. (I remember how long it took to get this fix in in the first place...)
Comment #56
xjmFiled #2345793: Entities should provide a method for a properly generated link to the entity.
Comment #58
tim.plunkettJust fixing that, not addressing #55
Comment #59
Wim LeersI think I might have done those. Those tests just need some links, it doesn't care links to what. In the new routing system, it's impossible to generate links to nodes without enabling the node module. This test doesn't yet enable the node module, so that'd be slowing the tests down for no reason. Hence I just switched them to other links.
Comment #60
Wim LeersPushing this one further.
Comment #61
Wim LeersThis comment is an attempt to go over ALL outstanding problems/questions/remarks and answer them, to determine what we still have to do in this issue before it can be RTBC.
In #37, I wrote:
#href
are for external URLs, and hence we need #2343759: Provide an API function to replace url()/l() for external urls to fix those.l()
becauseViewExecutable::getUrl()
returns a path. So both of these are safe to do at a later point; they won't affect an important public API.#44.1/#44.4/#44.5 are answered by #45.
#44.2: I wondered the same. And indeed, that code can be deleted altogether once they're views. But for now, we have to keep it around.
#44.3: :)
+1
#55.1: I thought this also at first, but… you only need to pass it through some sort of "external URL builder" or "external URL manipulator" or "external URL tweaker", if you're actually manipulating the external URL. If we're really using an external URL verbatim, then there's no point in passing it through a function.
#55.2: answered in #59.
CONCLUSION: This is RTBC.
… except that one of the blocking issues (#2344487: Provide special route names for <current> and <none>) landed in the mean time, so now we can convert those. But to do that, I opened #2346103: Convert url() and l() invocations using current_path() and '', since we have to do it not only for
l()
, but alsourl()
, which has many more of those cases anyway.Let's get this committed, just like we got #2340251: Remove most remaining url() calls, which also didn't convert everything, but represented massive progress, just like this patch does.
Attached reroll is only for chasing HEAD, no changes.
Comment #62
Wim LeersComment #63
webchickNice work! Onward to beta. :)
Committed and pushed to 8.x. Thanks!
Comment #65
martin107 CreditAttribution: martin107 commentedFound a broken $this->l() in core. I guess as they arise they can be Zapped on a case by case basis