In Drupal 8, we should as much as possible remove reliance on Drupal system paths and instead work with routes and their parameters. To that end, l() should have a sibling,
which works like l() but gets the route name and route parameters passed.
Doing that is for example important for the menu system. If we need everything to be l() / rely on direct paths, checking access is quite expensive. Currently menu_item_route_access()
takes the url and gets the original route_name and route_parameters to check access, while without using the path in the first place, this information is already present and we can check access directly.
API Additions
There is a new service called "link_generator" which provides a render method. $link_generate->generate() takes the text,
the route name and parameters as well as the options like l() does.
Before you used something like that:
$items['create_account'] = l(t('Create new account'), 'user/register', array(
'attributes' => array(
'title' => t('Create a new user account.'),
'class' => array('create-account-link'),
),
));
After
$items['create_account'] = Drupal::linkGenerator()->generate(t('Create new account'), 'user_register', array(), array(
'attributes' => array(
'title' => t('Create a new user account.'),
'class' => array('create-account-link'),
),
));
Follow-up tasks
- Convert menu links and any other code that does access checking with link rendering as soon as possible
- Optimize the doGenerate() method and make sure our route path restrictions are documented and enforced
- Create follow-up issues to convert existing l() and url() calls to route-converted pages once the optimization is done.
- Start including conversion to route-based links as other pages are converted to routes.
Related Issues
important:
#2051889: Add route parameters property to menu links
follow-ups:
#1965074: Add cache wrapper to the UrlGenerator
#2073811: Add a url generator twig extension
#2074297: Optimize the code in doGenerate() in the UrlGenerator to take advantage of Drupal path restrictions.
#2073813: Add a UrlGenerator helper to FormBase and ControllerBase
#2073779: Add tests for admin/reports/fields/views-fields and admin/reports/fields/views-plugins
#2078285: Add short-cut methods to the \Drupal class for generating URLs and links from routes
somewhat related issues:
#2046737: Add a method to the AccessManager that only needs a route name and parameters
#1979468: ".active" from linkGenerator(), l() and theme_links() forces an upper limit of per-page caching for all content containing links
#2073813: Add a UrlGenerator helper to FormBase and ControllerBase
| Comment | File | Size | Author |
|---|---|---|---|
| #154 | link_generator-2047619-153.patch | 36.88 KB | pwolanin |
| #154 | 2047619-151-153.increment.txt | 1.54 KB | pwolanin |
| #151 | link_generator-2047619-151.patch | 37.06 KB | dawehner |
| #151 | interdiff.txt | 861 bytes | dawehner |
| #139 | route.png | 136.73 KB | dawehner |
Comments
Comment #1
pwolanin commentedHere's a quick 1st pass. Marking this critical, since we need to really move to orienting all out code to use routes (or frankly roll back to the D7 menu system before it's too late).
Comment #3
dawehnerAs talked on IRC, lets introduce a LinkGenerator.
Comment #4
jibranNeeds api.php entry
Comment #5
pwolanin commentedI'd prefer something like render() to generate(), since we are returning HTML.
The code comments are still a bit incoherent (my fault).
Is this going to be a service? I don't think people will want to construct an instance every time?
Comment #6
dawehnerComment #7
Crell commentedTagging. Peter, please update.
Comment #8
dawehner.
Comment #9
tstoecklerLooks pretty cool overall. Some minor remarks:
Missing newline.
"Because this service is called very often, we statically..."
(l() -> this service and missing comma)
Do you mean "in a language different from the current one" (i.e. the word language is missing?)?
Comment #10
dawehnerThank you for your review! I removed that todo, because it is using the path processor both in url_generator->generate() as well as url_generator->generateFromPath()
Additional here is a unit test.
Comment #12
dawehnerUps.
Comment #14
dawehner:(
Comment #15
tstoecklerMight be better as a follow-up, but it would be cool if we could add a Drupal::link($text, $route, ...) for use in procedural code.
Looks great now, but I don't feel comfortable RTBC-ing myself.
Comment #16
dawehnerHere is the follow up: #2051813: Reuse the link generator to provide a Drupal::link() function
Comment #17
Crell commentedThere is no rl() function. This message seems out of date.
This may have been discussed above, but render() seems like a horribly obscure name for "gimme a link".
Comment #18
dawehnerNo idea about the naming issue, but my gut feeling was consistency with the url generator and use generate().
In general we have to figure out how we integrate external links. I guess we want to use a "renderExternalLink" or something like that method?
Comment #19
jibran@todo will help.
Comment #21
dawehnerAdded a todo. I guess the failures was just some randomness on the testbot.
Comment #22
pwolanin commentedWe should remove all code related to "Determine whether this link is "active'". It seems there is a clear consensus to move it to JS for l() and/or limit it to tabs and other navigation?
If Crell doesn't like $obj->render() how about just $obj->l()?
Comment #23
dawehnerCan you link some issues for that? I haven't seen it yet. This would be great in terms of cachability, but things like the active menu tree will not be trivial. I would suggest to keep this out of the patch, as long we don't have a replacement for it.
We could also go with $link_generator->link() or $link_generator->generate(), l feels wrong, as it does not tell anything at all, unless you are familiar with Drupal before.
Comment #24
tim.plunkett#1979468: ".active" from linkGenerator(), l() and theme_links() forces an upper limit of per-page caching for all content containing links
Comment #25
dawehnerLet's go with ->link for now.
Comment #26
jibranCopy paste error.
Comment #27
dawehnerI blame storm!
Comment #28
webchickAs far as I can see, that issue summary still has a big hole in it, so re-tagging. I'm especially curious why this is critical, and an API change being proposed well post-API freeze. It also seems like rl() is no longer the suggested fix, so the title likely needs an update as well.
I don't see any lines in this patch that actually remove a call to l() and replace it with ... whatever you would replace it with. So what would be the equivalent of this now:
Comment #28.0
webchickupdated the summary
Comment #29
dawehnerupdate the issue summary and now the title.
Technical seen this is kind of a api addition at that point but yeah on the longrun it should replace the system.
Comment #30
tstoecklerI think we can improve the wording here. Also the usage of @deprecated does not follow current standards. Suggestion:
Superfluous newline.
Is missing the "(optional) " part. I was about to remark that this could be a bit more descriptive about what the $parameters actually should be, but that is not documented at all in \Symfony\Component\Routing\Generator\UrlGeneratorInterface::generate(), so... :-(
Reading the code, I think 'query', 'absolute' and 'language' are supported as well.
This needs API documentation. I was going to copy it from hook_link_alter(), but that is not documented anywhere! It's also very strange that the altering takes place so late, as e.g. changing the langauge of a link does not work anymore at this stage, but that's also taken from l() directly, so should be a separate issue. For instance calling $variables['route_name'] instead of $route_name directly makes no difference in the given snippet because the alter hook hasn't run yet.
Even though drupal_render() being a class is still miles away, since this is the only call to a procedural function in this class (unless I missed something) I think a @todo couldn't hurt.
Why is the disableOriginalConstructor() call needed? For reference the constructor of LanguageManager is this:
We should also test the 'html' and the 'absolute' option.
I think the indentation is off here. Also, I think both arrays should be formatted equally for legibility.
Also, what is this 'value' thing? If I get it correctly, this is testing that the $parameters get passed correctly to the URL generator. If so, there should be a comment for that.
We cannot test that the active class is added on the front page for links to the front page because drupal_is_front_page() is not yet a service. There should be a @todo for that.
We should test however, that setting a different language removes the 'active' class and that different query parameters also remove the 'active' class.
Super minor, but missing parentheses after the function name.
I'm going to work on this a bit and also improve the title and issue summary.
Comment #31
tstoecklerPer @dawehner, we should also test that the hook_link_alter() is fired.
I also cross-posted with him, he already updated the issue summary and title.
Will work on the patch now.
Comment #32
tstoecklerUpdated docs, did not add additional tests yet.
Also opened #2055711: Invoke hook_link_alter() earlier in l().
Comment #33
webchickYeah, that needs to be Drupal::link(), not Drupal::service('link_generator')->link(). That function gets called literally all the time, so definitely fits into the definition of public-facing API, and thus should have a first-level method.
Comment #34
dawehnerreadd the title.
Comment #35
dawehnerThanks for your review and fix!
Added a Drupal::linkGenerator() to match Drupal::urlGenerator().
Comment #36
fabianx commentedThis will need profiling as links are heavily used within sites.
Comment #37
thedavidmeister commentedSo, how do I use this with the Render API?
This patch makes a l() "equivalent" without making equivalents for any of the "higher level" functionality that uses l() currently.
We're "halfway through" cleaning up our #type 'link' ish elements in the theme system:
#2043649: Make all #type 'link' arrays 100% renderable, use #theme_wrappers if necessary
#1595614: [meta] Remove all the theme functions and templates in core that simply output a link. Replace with #type 'link' render arrays
#2052253: [META] Add #render property to drupal_render() and convert #type "#pre_render -> #markup" calls to use it
#2044105: Introduce #alters for \Drupal\Core\Render\Renderer::render()
If l() is deprecated, what is the proposed "correct way" to render links through the Render API as #type 'link' will be using deprecated functionality with the current patch and is therefore presumably no longer "correct"?
Comment #38
fabianx commentedRe-adding tag.
Comment #39
dawehnerWhat about don't mark it as @deprecated for now, but wait until you have reworked theme_link and go, because
this allows us to make some progress in the meantime.
Comment #40
tstoecklerYeah, I agree with the notion. I removed the @deprecated and added some inline comment that the function is discouraged for internal use.
I added a bunch of test cases. This gets us to 97% test coverage. The remaining one line that supposedly isn't covered is I think due to different possibilities of passing the $options parameter. I.e. you can pass 'language' and 'html', or 'absolute' and 'query', or all of them, ... I didn't go so far to test all possible combinations, I think that would be a bit academic.
Will profile this now.
EDIT: I also took the liberty of splitting up some long lines, I hope that doesn't put anyone off.
Comment #41
thedavidmeister commentedI'm also not sure how I feel about the alter using "route_link" instead of just "link"...
I understand that $route_name is different to $path but everything else should be identical to l().
The alter in l() was intended as a compromise for preserving flexibility of markup when we removed theme_link(), meaning that anyone who would be implementing that alter would be expecting it to apply to all links regardless of the API used to generate the displayed href.
Also, I have to ask, as I can't see anything in the summary or comments but do we really need a whole new function to meet the requirement of "remove reliance on Drupal system paths and instead work with routes and their parameters" - couldn't l() be reworked a little to try one method of generating the href from a path or route by setting a flag in $options to TRUE/FALSE?
maybe even if $options has 'parameters' set, just assume that it's a route?
Comment #42
dawehnerWe introduce a new alter hook, so that is not really a problem. Hopefully we will be able to remove l() on the longrun.
So, we want to clearly separate the current system from the new system, because having something like l('String', 'route_name', array('parameters' => array())) is really a problem. As said before, we slowly have to make progress and can't wait years to get it done right at the first place.
Please now.
missing documentation, damn
he => The
Comment #43
thedavidmeister commentedNo, that *is* the problem, it's not removing the problem >.< In that, if I want to modify the attributes (for example) for all links on the site I now have to implement two alters where currently I have to only implement one.
There's no documentation around the two different alters at all. Not even an inline comment in l() above its alter(). I believe it makes using the alters hard to use and harder to learn about in the first place.
On a related note, actually implementing the alter in core to achieve something that could be inlined into the new function does look like a performance hit for no extra benefit.
There's no theme_link anymore, there's only #type 'link' that uses l() in #pre_render to generate links. We're not "reworking", we're mid-late conversion based on something we already have reworked, which was removing everything that previously generated links in templates and theme functions (implementations which would not have cared so much about plans to remove l()) and converting them to all use l() for everything (which is now awkward).
For all practical purposes #type 'link' is just l() by another name. Why would we discourage l()'s usage here while simultaneously actively working to increase it's usage somewhere else?
I have no idea how #type 'link' should work any more. It is based on l() so therefore it is "discouraged" for internal links, but is required for external links... how could we know when defining an element type whether #type 'link' is going to be used for internal or external links?
The docs say nothing about what to do if I don't know whether the link I'm generating will have an internal or external href. Should #type 'link' be trying to detect "externality" based on its input and call different link generation functions depending on what it thinks it has been passed?
In truth we would probably need #type 'link' for external links and to introduce #type 'route_link' for internal links so we don't have to guess, but then the Render API has to have near duplicate rendering implementations for the same markup. This is undesirable if it can be avoided as themers don't care about routes vs. paths vs. external links and the extra complexity of seemingly duplicate element types will just cause confusion and misunderstandings.
Unfortunately, I don't know enough about the new route system to answer this question myself, but if we need a "new l()", can it be written to support external URLs as well without causing problems elsewhere? if we can do that it would become relatively easy to re-write #type 'link' to be based on routes instead of l() as part of the general theme system cleanup.
Comment #44
fabianx commentedWhat about keeping things as is, but allowing l() to take an array instead of a string, where:
array == route with parameters
string == old style link (whatever that means)
That would map nicely to what we have now and is compatible with #type.
Also could we get some extra examples, where the needed parameters should / need to be used?
There has been much consensus in the community that keeping l() is a good thing (in different issues related to the theme system), so I can't see the need here and why it is that urgent?
And if all needs to changs, why would l('A Node', 'node/1') or url('node/1') still work at all?
Comment #45
pwolanin commented@Fabianx - yes, I had thought about that originally. I agree the DX of having 2 different functions for internal vs external links is not great.
The urgent need here is the principle that we want to refer to routes by name and that any use of "system path" should be considered deprecated.
Of course, the reality is still a ways away from that.
Let's me try rolling this into l(), even if we use this class inside
Comment #46
pwolanin commentedOK, so let's step back and summarize where things actually stand:
.
Some possible ways forward:
Comment #47
pwolanin commentedok, so based on the feedback here, let's switch gears to an alternate issue and try to keep l() and url() similar to now with possibly passing an array arg in for outes, and marking as @deprecated all methods for using system paths we can find:
#2057155: Add a generateFromRoute() method on the url generator to accept options like url()
Comment #48
dawehnerBack to needs review ... All the url related things are unreleated. We could get the link generator in first and then deal with everything done by #2057155: Add a generateFromRoute() method on the url generator to accept options like url()
Comment #49
tstoeckler@thedavidmeister: Let's leave '#type' => 'link' out of this completely, please. I'm aware that it uses l() for internal paths, but that doesn't change the fact that contrib modules should not do that. We will figure out what to do with '#type' => 'link' in a separate issue. Baby steps.
@thedavidmeister: The fact that hook_link_alter() and hook_route_link_alter() (which *is* documented, btw) are similar and modules might end up needing to implement both in the same way is unfornatunate, I completely agree with you. That should not stop the introduction of this hook, however. If we move the remaining use-case of l() (generating a link from an (external) path) onto LinkGenerator as well, we might even be able to consolidate the two hooks. As I already noted above, the calling of these alter hooks is very strange anyway, as it allows to alter less things than you would generally expect. So again, let's please just get this in, and improve this in a follow-up.
I don't know if you're aware of it, but this patch among others is blocking getting the config_translation.module in core, which is a critical feature for Drupal 8. So making progress in baby steps is not just a matter of preference but it is really important in this case. Thanks!
@pwolanin: Thanks for splitting out the UrlGenerator-related parts into a separate issue. Luckily, because LinkGenerator already has $options built-in in the current patch the changes for this issue will be minimal.
Comment #50
pwolanin commentedFixing the title, since the patch is now just about adding a link generator.
I think core needs this to hold the functionality currently provided by l() - probably l() like url() would reamin as a well-known helper function to just call this.
If we get this in now it's going to require a bunch of follow-ups, but maybe that's fine.
Comment #51
thedavidmeister commented@tstoeckler, I'm not sure if this was your intention, but this did come across as a bit inflammatory. I'm not trying to ruin anyone's dreams or block patches because of ???, I'm allowed to voice my concerns with the quality/implications of a patch regardless of what it is blocking.
Before the recent name change of the issue, this was partially about deprecating/discouraging a function that we were actively increasing the usage of elsewhere. That's a valid concern, I believe.
I am happy to figure out what to do with #type 'link' in a separate issue, as long as that separate issue is followed up soon after this goes in and we're not planning to launch D8 with a key part of the Render API built upon something we're telling contrib modules they should be avoiding. @dawehner doesn't want to wait years for this "to be done right", fair enough, but I don't want to wait years for the ability to represent links as structured data to be re-implemented either if it's destroyed by a poorly executed transition between one method of link generation and another - if there's no compatible render element for the new generator then it is forcing "early render" which is sometimes ok and sometimes really not ok, depending on the context.
So, summary of my points against the most recent patch, even if we aren't deprecating/discouraging/modifying #type 'link':
- Introduces a new alter that is confusing as it splits altering links based on their href, I still don't see why we can't just use 'link' as the alter for both and leave it up to the alter implementations to decide whether they care about routes/paths or if they're just trying to add an attribute to the link. If we're really concerned that the alter won't be able to tell the difference, we could pass in some contextual information to the alter like 'route' => TRUE or something?
- Hasn't been profiled
- Is rendering markup and is only compatible with "early render", which is something we're trying to clean up in #2046881: [meta] Avoid "early rendered" strings where beneficial to do so, build structured data to pass to drupal_render() once instead and doesn't provide any way to avoid the early render (which would usually be the implementation of a render element type).
The last point I appreciate is probably well beyond the scope of this issue, but I hope others agree that it is something we want to address before D8 is launched and to not handball it to D9.
Comment #52
thedavidmeister commentedOh, one more thing and I'll leave this issue alone for a bit :P
- The docs shouldn't mention l() being deprecated/discouraged or recommend usage of the new link generator until we're confident that migrating directly from l() to the new generator will be possible in most, if not all, cases. At the moment, without being able to handle external links we should be more restrained in our recommendations. There will be times where developers legitimately won't know whether the link they're rendering will have a href that points to an internal or external URL. If we're going with the "baby steps" approach, this applies to the docs too.
Comment #53
pwolanin commentedI don't understand the case where you wouldn't know if you have an internal or external link? In the case of a route there would really be no "href", but a route name and parameters.
Comment #54
dawehnerYes, it is great that twig introduced a pattern of a performance test on every issue, but this is not the reality in any other place in the core issue queue.
Let's do that.
That is a total valid concern. ... Well, the contextual information is a difference, so having two hooks seems easier to understand.
Tobias tried, I hope he will manage to do it. There are some issues with drupal.
It would be great if you could just create a follow up, which describes what you have in mind!
Comment #55
thedavidmeister commentedWhat you're saying only makes sense if you make a separate #type 'link' and #type 'route_link', but then what if we want to make a #type 'more_link', do we then have to make a #type 'more_route_link'? In #1595614: [meta] Remove all the theme functions and templates in core that simply output a link. Replace with #type 'link' render arrays there are 11 functions/templates that used to be running through theme(), we've converted almost all of them to #type 'link', but the "next step" currently looks to be #2043649: Make all #type 'link' arrays 100% renderable, use #theme_wrappers if necessary which is to re-implement a bunch of them as their own element types that would be "derivatives" of #type 'link', so then we now need to potentially create 22 new element types to have 'link' and 'route_link' equivalents for everything?
This sounds insane to me, if the new generator could just handle external links, we can rewrite #type 'link' right now and all move forward with our lives.
I did say we can leave that to a follow-up issue though. Let's leave the conversation about avoiding duplicate render element types to that issue.
In the past I've been careful to get any changes to l() profiled and optimised because it is called very often #1922454: Gut l(), fix theme('link'), have a working framework for consistently generated links inside and outside of Twig and I have no doubts that others have been similarly careful with their changes. If your long term goal is to remove l() and replace it with a link generator, the generator has to be comparably fast - This has nothing to do with Twig, it has to do with the fact that the function you're aiming to replace has a history of being scrutinised for performance implications for at least the last 3 major releases of Drupal.
See D6:
D7:
D8:
All of the work that I linked in #37 is mostly inspired by a 10-20% speed increase in rendering links through the Render API over the previous implementation.
At one point we even vetoed moving some of the logic in l() into a public function that seemed useful (url_is_active()) because one extra function call in l() was deemed "too slow" after profiling... that's more or less equivalent to the alter added in this patch. So at the very least, based on past profiling of l() I know that the new link generator is doing something we disallowed in l() previously for performance reasons so for that reason alone we should be inlining the logic that's currently in hook_route_link_alter() in the most recent patch.
Please, if "there are some issues with Drupal", that make profiling impossible, a clear report outlining why profiling is impossible would be a hundred times more useful to others who may want to verify or refute that claim than offhand comments about why it's too hard and some parts of the issue queue can ignore performance and other's can't.
I see the comment about l() being discouraged is still in place too with no further discussion...
Marking this needs work because:
I'm going to have a crack at a re-roll with the last 3 points addressed. I don't want to be the guy who just complains and doesn't help out ;)
Comment #56
thedavidmeister commentedComment #57
dawehnerThank you very much!
Comment #58
alexpottIt's vital that this change is profiled since l() is on the critical path for many sites. Let's make sure we don't skimp on it here.
And @dawehner
You're correct that it is not reality for many issues, but that does not make it right. For example, it really shouldn't be the twig team that finds issues such as #2051847: AnnotatedClassDiscovery::getDefinitions is critically slow when viewing nodes with comments.
Comment #59
thedavidmeister commentedCompletely ignore what I said there. I actually thought that hook_foo_alter() did something, but I tested it and it clearly doesn't unless it's actually a module implementing the hook, I guess I'd never tried it before or really had a reason to look into it, so I was just totally wrong about how this works >.<
Apologies for any confusion caused by my earlier confusion.
Anyway, here's an updated patch that builds on #54 by updating the docs to be clearer about when to use and avoid l() and the generator and merging the alters into just drupal_link_alter() - I think if we can assume #route_name gives us enough context in drupal_pre_render_link() we can probably assume that 'route_name' gives us enough context in drupal_link_alter().
@dawehner, you've already done this by adding route support to drupal_pre_render_link() in #54! no need for a followup.
What this means is that we can now do this:
Which means $link can be rendered elsewhere/later and therefore is no longer "early", in the context of #2046881: [meta] Avoid "early rendered" strings where beneficial to do so, build structured data to pass to drupal_render() once instead.
Comment #60
thedavidmeister commentedComment #61
tstoecklerWow, I missed a lot.
@thedavidmeister: I'm sorry that I sounded inflammatory. I apparently failed to communicate that, but I agree with almost all of your concerns. It was just that the concerns were not actually technical but theoretical and I was afraid that resolving them would greatly broaden the scope of this issue, which is already pretty hard. @dawehner and you now have found a way to (I assume) mitigate your concerns without reworking the patch, at least not in a significant way. So, progress++
Comment #62
webchickRepeating myself from #28, can someone clarify in what way this is a critical issue, and what the compelling use case is that requires it? In absence of that, it might better to have this provided by contrib at this point, because it sounds like it's already confusing the heck out of people about which "link" function to use when and how.
This:
...scares the bejeebus out of me. We already have about 50 of these "bunch of follow-ups to implement new API x" issues, most of which are between 0% and 30% done, and all of which will cause Drupal 8 to be harder to learn and approach for new people if they're not all 100% done by release. And given we have over 100 critical issues to sort out before then, I'm not keen to add more things to that list.
Comment #62.0
webchickupdate
Comment #63
dawehnerAdded an example why this is helpful. In general we went with using the url generator so why should we stop in the middle of it. Seriously, some things are desperate dis-motivating.
LOL.
Beside from that this patch moves things to be lazyloaded as well as introduces a full testcoverage of it, which did not really existed in that detail before.
Comment #64
Crell commentedwebchick: We are moving toward "route names everywhere". Right now, if you have a route name, there's no way to create an A tag out of it. That's a critical omission that results in very inconsistent and confusing code.
Saying "well always use route names, except in links where you have to use a path, but here the path has a leading / and over there it doesn't, but that's a route name so you have to do it this other way" is NOT a developer-friendly situation. :-) It is, however, the current situation. This issue is trying to help fix that.
Comment #65
dawehnerWe could also generate the url with the url generator and pass it along to the l function, but don't ask how this code will look like...
Comment #66
dawehnerThank you @thedavidmeister for the patch in #2047619-59: Add a link generator service for route-based links
The active class issue seems to need some time and seriously removing the code here will be trivial.
@tstoeckler
Do you have some time to make some benchmarking?
Comment #67
pwolanin commentedre-roll for HEAD offset. Also cleans up code comments and fixes a bug that the active check was using the result of generate() rather than getPathFromRoute().
I think this change makes it double apparent that we don't want to support active, but we need resolution at #1979468: ".active" from linkGenerator(), l() and theme_links() forces an upper limit of per-page caching for all content containing links
Comment #68
pwolanin commentedoops, reading the 'active' issue again, looks like that might be retained for menu links, so ignore #67 - this has a more complete code comment.
Comment #69
dawehnerJust in general it seriously feels wrong to have to call the url generator twice, but as this will be temporary anyway, no deal.
Then mock the interface defined by drupal instead.
Comment #70
pwolanin commented@dawehner - can't mock the Drupal interface - it's not complete now (doesn't define all the generator methods). We're waiting on the options generator patch still!
We shoudl define an interface for this service also and use route instead of path.
Comment #71
ParisLiakos commentedDisclaimer: I didnt read all the previous comment.
Besides the missing interface, which i already talked about in irc:
i dont feel right with this..How about separating the HTML part from the rest of the logic?
Because we effectively make this service useless if one doesnt want to generate HTML.
And by separating the logic, means we could reuse it in l(), because how i see it now, its more or less the same.
eg:
have methods:
The interface should have all the methods above besides renderHtml, so you can rely on those being there
Comment #72
pwolanin commented@ParisLiakos - I'm a little confused. We have the UrlGenerator if you just want a link.
We need something like l() for routes, but we are assuming we won't break the existing l() signature now. I'd be happier to do that if you can convince the committers.
Comment #73
thedavidmeister commented#71 sounds similar in concept to #1985974: Make l() optionally return structured output.
There have been attempts to abstract the inner logic of l() into different public functions in the recent past during the Twig conversion and they've all hit walls on the performance gate, but maybe it makes more sense here.
Comment #74
ParisLiakos commenteda ha thanks for the link #1985974: Make l() optionally return structured output
yes, what i am saying, is that
a) we duplicate code
b) we mix, logic and alter hook with html
c) i have no way to run the same logic (eg find whether a link is active) if i dont want to generate html
Comment #75
ParisLiakos commentedalso, no need to change the signature of l()..just make it internally call
return theme('link', \Drupal::service('link_generator')->buildFromPath());it keeps same arguments and return the same thing..it is true there are more function calls, but in my books, maintainability comes first, not CPU cycles
Comment #76
pwolanin commented@ParisLiakos - getting something in place to start handling #type => link for routes is really the critical part. Let's hold more refactoring until after that.
Comment #77
thedavidmeister commented#75 - heads up that theme() is deprecated. Using '#type' => 'link' inside l() and friends would be great rather than the other way around IMO, but that's a discussion for another day.
#theme => 'link' does not exist.
Comment #78
pwolanin commentedget closer, but the test needs to be fixed.
Comment #79
pwolanin commentedComment #80
thedavidmeister commented@ParisLiakos - if you had read the issue thread you would have seen #54, #55 and #58 where it was made crystal clear that if we are proposing a replacement for l() then it has to perform comparably, or at the least profiled carefully so we know the extent of the damage. The performance gate is an official part of getting patches into core - https://drupal.org/core-gates#performance - "maintainability", while important, is actually not a core gate.
Comment #81
thedavidmeister commentedgetting the testbots on #78 and #79 so we can see.
Comment #82
ParisLiakos commentedwell. if we care that much for performance and function calls here then we shouldnt do it as a service, but rather a simple function.
but well, i will now shut up and stop derailing this issue.
Comment #84
pwolanin commentedThis doesn't fix the test, but uses a more meaningful comparison for the arrays (I don't think a simple == works as expected).
Comment #85
thedavidmeister commentedWhat's wrong with ==?
Comment #86
dawehner"==" for example has problems with the order.
Fixed a couple of small points and the tests.
Comment #87
thedavidmeister commentedI don't think == has problem with the order? We have tests in place for l() having a different order in the query that work fine with ==.
See https://drupal.org/node/1922454#comment-7187362 for discussion + existing tests.
Comment #88
thedavidmeister commentedThere's no tests for order of query parameters that I could see in #86.
Comment #89
thedavidmeister commentedComment #90
Crell commentedThis seems overall sane. Some comments below.
Shouldn't we be turning l() into a dumb wrapper around this service, as we've done elsewhere?
Possible scope creep, but at some point we should change the "html" property as it is horribly misleading. In know in theory it means "this contains HTML, so trust me, really", but it's completely non-descriptive of what is *actually* implied, which is "don't check-plain me, trust me on the content".
When moving to a new API anyway seems like the logical time to fix that, but I suppose others may disagree.
This method looks like it really ought to be using @dataProvider instead. If not, it should be broken up into separate methods.
(Separate methods in PHPUnit are super cheap compared to Simpletest, and are a good practice. A zillion tests in one method is only something we do in Simpletest to side-step its performance design flaws.)
Ibid. This definitely feels like it needs a @dataProvider.
Comment #91
thedavidmeister commentedThat would require converting every l() and #type => 'link' to use routes instead of paths in this patch, wouldn't it?
yes, scope creep indeed ;) What property name do you propose?
Comment #92
pounard"escape" with a default to true?
Comment #93
pwolanin commentedChanging the semantics of the options seem like they should be a separate issue.
Comment #94
dawehnerHere is a followup: #2070119: Switch from html to escape in the l() function / link generator
Yes, we can't do both at the same time.
One possible follow up would be to move the code of l into a linkFromPath method on the linkgenerator, but I would like to also do that on a follow up, as it will cause also more discussion
Follow up: #2070147: Move l() into the linkGenerator
I haven't reworked the testLinkActive method as it is will be removed in the future anyway. We have a basic good test coverage
Let's do it and also split up the tests into logical groups.
Comment #95
thedavidmeister commentedOk, I was just going on the assumption that we'd like to have at least equivalent test coverage to l().
Also, I'm concerned that the new active class comparison introduced here is different to l(), has no tests, and looks like a perf hit. Adding the active class server-side may or may not be removed, it hasn't been decided yet #1979468: ".active" from linkGenerator(), l() and theme_links() forces an upper limit of per-page caching for all content containing links.
Comment #96
tstoecklerI tried profiling this. I just a dumb scripts that runs l() and Drupal::linkGenerator->link() a bunch of times in a bootstrapped Drupal. Calling each function 1000 times and then doing that test 100 times I get a 4% performance hit. Calling each function 10000 times I get a 11% performance hit. That's kind of strange, so I don't know if I did something fundamentally wrong or not. Anyway, someone should xhprof this or something.
Comment #97
tstoecklerHmm, that was unintentional.
Comment #98
thedavidmeister commentedhmmm, passing no judgement here, but wanting to provide some context, we removed theme_link() partially because profiling showed it was about 5% slower than calling l() and the original Twig template conversion issue that was proposed for theme_link() was completely blocked on a 10% performance difference between the template and l().
Conversions for Twig are/have been considered "not comparable" if they result in more than about 0.5% difference in perf.
Arguably, this service is more useful than theme_link() was, but those numbers aren't encouraging :( I think I'd like to have a crack at doing some similar basic test runs with timers to see if I can replicate that.
I might try a test that involves different languages, "active-ness" and paths/routes, including absolute URLs and query strings to make it a bit more representative of a page with variation in rendered links?
Comment #99
tstoecklerYeah, in case that didn't come across, I wouldn't really advise anyone to trust my numbers.
Comment #100
dawehnerJust in case someone stumbles upon that: Also the active part has a test coverage.
We have to figure out where this performance problem might be. If it is loading of the route objects, maybe #2058845: Pre-load non-admin routes would help in reality.
Comment #101
thedavidmeister commentedThis changes the behaviour of active class generation from l() in two ways:
- The order of checking conditions has moved query checking above language, this is a perf hit for sites making heavy use of languages
- == has been replaced with a more complicated method of checking equality
There were no failing tests demonstrating a problem before either of these changes were made.
There is no test coverage in the patch equivalent to the following test (in UrlTest.php) that already exists for l():
What I'm saying is, there's no proof that we need to replace == with
(count($full_parameters) == count($this->active['parameters']) && !array_diff_assoc($full_parameters, $this->active['parameters']), the only reason given is that == is sensitive to order, but the test that already exists for l() shows it isn't, and I'm concerned that the latter is a perf hit.The PHP docs say that == just checks for key/value pairs and only === check for order - http://php.net/manual/en/language.operators.array.php
Comment #102
pwolanin commentedRe-rolling to put back "==" and doing it after language so the check is the same as l()
While we need to look for the performance difference, this is a critical, must-have piece of functionality for the conversion to routes and is blocking about 10 other issues!
Comment #103
thedavidmeister commentedAs @dawehner said in #100, we have to figure out where the perf hit alluded to in #96 is actually coming from before we can discuss it meaningfully. It's important to know whether some part of the patch is causing a slow-down and needs fixing or if it's the route system itself being slower than the path system - in which case there'd not really be anything further we could do here.
What I was talking about in #101 would have nothing to do with #96 as the benchmarking there had no language or query active URL checking in the test, but thanks for reigning in the scope creep a bit @pwolanin :)
Comment #104
dawehnerRerolled against latest core.
Comment #105
dawehnerThe third parameters should be just the options. Ensure that we have test coverage for that as well now.
Comment #106
pwolanin commentedFixing code comments and making the code and flow more exactly match l(), and fixes the test to match, since e.g. attributes are not passed to url() they should not be passed to the generator.
https://api.drupal.org/api/drupal/core%21includes%21common.inc/function/l/8
Comment #107
pwolanin commentedAs a follow-up I think we should consider the naming of and add more directly useful methods to \Drupal, e.g. Drupal::link() or even Drupal::l() instead of making developers remember how to get the generator and which method to call
e.g.
Comment #108
dawehnerThere is already a follow up filled for that: #2051813: Reuse the link generator to provide a Drupal::link() function
Let me readd a test for passing in query via the $parameters.
Comment #109
pwolanin commentedtagging
Comment #110
Crell commentedI am comfortable with this.
Comment #111
thedavidmeister commentedstill needs profiling
Comment #112
dawehnerThe issue is still assigned to you :p
Comment #113
thedavidmeister commentedwoah, that's from #56
Comment #114
pwolanin commentedI don't think this issue needs profiling on its own - it's just using existing functionality (look up routes, call a hook, run the url generator), so what would you compare it to?
A comparison to l() alone isn't exactly meaningful since this will have to load the route from the DB (or cache?) if it's not already loaded. However, loading the route should be faster than running the url matcher against a path if you need to do access checks.
Comment #115
thedavidmeister commentedIt's meaningful because in this patch we're still recommending in the docs for l() that developers use this link generator instead of l() without explaining (or even knowing) the performance consequences of using one over the other (In the D7 docs for l() we were careful to explain to developers the performance consequences of making changes to the usage of l()).
It's meaningful because the consumers of Drupal, end-users, site owners, developers, do not care so much about the "why" if something that was previously fast and *should* be fast suddenly becomes slow. The question here is very simple, "how long does it take to render a link, using the standard core mechanlsm?".
It's meaningful because contributors have suggested alternative implementations to a service that may potentially be faster if a signifiant perf hit is identified, including an rl() procedural function or adding route support directly to l().
It's very obvious from this issue thread that getting community approval for this patch is not just about the direct impact of this patch, but the implementation has to function as a starting point for a long term goal to completely replace l() throughout core and contrib with whatever is implemented here. There's no point pretending that this is a simple "commit and move on" situation just because we have a good quality, green patch and RTBC is in sight.
If the profiling comes back showing a significant slowdown, this would impact what recommendations we make and how far we should take the service. Do we just use this to unblock other critical issues that directly rely on this patch and revisit the problem in D9, or do we immediately push for all of core to use this service before launch? @webchick has already voiced concerns in #62 over "what happens next?" here, the potential perf hit of using routes instead of l() will surely temper how enthusiastically we push any future conversion issues. How slow would be "too slow"? 10%? 50%? "too slow" meaning we would have to start re-writing things here even if we have to sacrifice DX, or even postpone this issue on other issues that make route lookups faster such as #2058845: Pre-load non-admin routes linked in #100?
The core gate docs for performance outline the reason to profile a patch for CPU with XHProf:
That is an excellent description of exactly what we're doing here. If we absolutely can't use XHProf, #96 shows we can still get some basic benchmarks without it that may suffice.
In #58 @alexpott said this:
So, if you don't want to profile, please don't just say "IMO we don't need to" and hit RTBC but clearly explain how/why this patch is exempt from passing the core gates, and why we should be ignoring what @alexpott has already said about comparing the performance of a route-based service to l().
Comment #116
pwolanin commentedI am happy to profile separately and work on performance, but this patch is in the critical path for making progress on route conversions. To the extent there is a performance difference it's about the difference between using a route-based system vs system-path matching and honestly it's pretty meaningless to profile the effect of a small part like this versus profiling and optimizing the overall system.
As best as I understand it, alexpott and the other maintainers have made a commitment that Drupal 8 will use routes and only routes by the time it's released. This patch puts in place a small but critical piece of functionality to support that. This means we will not be using l() for almost anything by the time D8 is released - once pages are converted to routes, any code generating a link to them needs to use the route and parameters, and so l() is not an option.
An existing issue to improve the generator performance by caching is at #1965074: Add cache wrapper to the UrlGenerator
As you see at the start, I suggested we mark l() deprecated. I guess that was a little too far, since it could possibly sill be used for external links, but essentially it's not longer going to be relevant for most use cases.
Comment #117
webchickI definitely didn't make such a commitment to using routes everywhere in D8 (not sure if Alex/catch/Dries did, or where it was publicly stated), especially given how late we are in the cycle and how much WSCCI is contributing to the overall release blockers ATM. There is certainly agreement among core maintainers to not ship D8 with two routing systems, but that just means finishing #1971384: [META] Convert page callbacks to controllers, which is already critical and in-progress.
If committing this patch does indeed set us on a path to oblierate l() usage throughout core, profiling is a must-have prior to commit. There was already extensive work done by the Twig team in order to make l() nice and speedy; we need to make sure that work is reflected in the "new world order." The last thing we need is yet another vector where we need to try and claw back performance.
Comment #118
Crell commentedThere are plenty of places in code where we'll have a route name but no path. In order to get an A tag out of that, you'll need to call the generator with that route to get a path, then use that path (give or take some string mangling for leading slashes) with l().
This service is just wrapping up that process to make it easier, as I understand it. The same amount of work / same performance impact or lack thereof will be present either way. We can either make that process easy, or make it hard/obtuse. I'd rather make it easy.
Whether we commit to removing all traces of paths from core or not is irrelevant. We will need to go from route name to A tag at times, and this service makes the process of doing so easier.
Comment #119
dawehnerLet me try to make a point that this improves the DX:
Some example code to provide a link with a path coming from the routing system.
After the patch
Comment #120
pwolanin commentedI fell like I've been hearing very clearly that in D8 we are not shipping until the old menu routing system is gone.
Once it's gone hard-coded system paths (i.e. a call to l()) should not be used to identify the "route" since the the path rendered for a certain route could be changed at any time by changing the route definition.
msonnabaum suggests that we also pull the contents of l() into this service so we have the code in one place, but I'd rather do that as a follow-up (as well as short-cut static methods for procedural code as described above).
The code example from dawehner is a not only a bit of a straw man but also a performance hit since in l() it's calling the UrlGenerator again.
Comment #121
dawehnerChanged the patch to use generate() as it is a verb in contrasts to link and it makes it more parallel with the way how UrlGeneratorInterface looks like.
Comment #122
dawehnermissing interdiff
Comment #123
pwolanin commentedThis converts uses in the ViewUI controller to illustrate the change and also fixes it to use the correct UrlGenerator interface.
Note that the redirect response in that class is already done correctly using the route as:
Comment #124
dawehnerI really think this is just wrong. This couples the code a bit more drupal for no win at all, but I won't fight this.
You seem to have not tested the change ... you override the urlGenerator with the linkGenerator :)
Comment #125
pwolanin commentedOops - was testing the wrong page.
re: the UrlGeneratorInterface - we might e.g. prefer to use the options array, so I think any Drupal module code should use the Drupal-provided interface (that was recently committed).
Comment #126
dawehnerWell, but we don't need it here yet. Let's not fight here.
Opened an issue to write tests for this routes: #2073779: Add tests for admin/reports/fields/views-fields and admin/reports/fields/views-plugins
Comment #127
dawehnerComment #128
dawehnerSome followups: #2073811: Add a url generator twig extension, #2073813: Add a UrlGenerator helper to FormBase and ControllerBase
Comment #130
pwolanin commentedThe conversion in DbLogController wasn't quite right. He's a working one.
Note that the suggestion that this patch needed to show the use of the service and add a method to ControllerBase were from msonnabaum via IRC.
Comment #131
pwolanin commentedplus some reorganization and minor fixes of the doxygen for the (previously missing) system.api.php entry
Comment #132
pwolanin commentedAnd a fix to the method name in the l() doxygen that was missed earlier.
Comment #133
pwolanin commentedHere's an incremental diff comparing #132 to the rtbc patch at #108. The meaningful (non-doxygen) changes are:
Comment #134
msonnabaum commentedChanges from today look like a big improvement to me.
Comment #135
dawehner+1 for these changes.
Comment #136
thedavidmeister commentedMay I propose an approach to getting the performance stuff ticked off so we can move forward? The important question here isn't whether routes or paths or absolute URLs are faster, or even what regressions are "introduced by this patch" and what is "external" to it, but simply:
Using the microtime() approach from #96 with X runs we could get a minimum and some middle ground, average + std dev? median? for:
1. l()
2. l() with an inline URL-from-route mechanism
3. A link generated by whatever the latest patch here implements when we test
for:
A. An internal path/route
B. An internal path/route with a language
C. An internal path/route with a query
D.
<front>If 1, 2 and 3 are all comparable (which would be no more than a 1-2% regression from this patch vs. l()) then great, we're done here, RTBC! :)
if 2 is "too slow" relative to 1, then we have an issue with the routing system itself being too slow and should postpone this issue on critical performance improvements to the route system that are likely to make up the difference.
if 3 is "too slow" relative to 1 and 2 then we have more work to do on the code in this patch and the route system can be left alone for now.
That's reasonable, yeah?
Comment #137
pwolanin commented@thedavidmeister - rendering based on route name *is* the right way. System paths are no longer the "truth" and shouldn't be hard-coded e.g. calls to l() - anyone can change a route path and invalidate a hard-coded path.
Would we decide now that we are going to roll back use of the symfony routing system based on one mico benchmark?
As Crell says in #118 - this functionality is required for Drupal 8 regardless of what any current profiling would indicate compared to l() (hence it being critical).
The last patch includes a couple views admin pages that could be profiled to give us an idea of the effect in a page, but that should inform the urgency of follow-up issues, not block this one.
Comment #138
dawehnerHere is a very naive benchmark code:
We could really trick a LOT in the benchmarks by using a cached url generator (which happens in another issue).
Comment #139
dawehnerHere is a benchmark which does not test the link generator but shows the potential we have when we use routes
instead of paths for menu links.
Yes it is unfair, because menu_item_route_access() triggers a DB call, but checkNamedRoute does not, but there is also way less php code to be called.
Comment #140
pwolanin commentedSo, the net result, looks like:
Comment #141
thedavidmeister commentedyay!
Comment #141.0
thedavidmeister commentedadded some explanation
Comment #142
pwolanin commentedI added a list of significant follow-up tasks to the summary based on the results above and discussion with thedavidmeister and dawehner in IRC
Comment #142.0
pwolanin commentedUpdated issue summary.
Comment #142.1
pwolanin commentedUpdated issue summary to add link https://drupal.org/node/2074297
Comment #143
Crell commentedI'm still comfortable.
Comment #144
pwolanin commented#132: link_generator-2047619-132.patch queued for re-testing.
Comment #145
webchickI feel like catch or Alex would be much better candidates to review/commit this than me, given the earlier performance concerns and my 3 month hiatus where I missed most of the stuff leading up to this patch. Not sure which is best to assign it to, so just leaving it for one of them.
Comment #146
thedavidmeister commented@webchick, the comments in this thread are a poor record of the conversation we had in IRC, apologies.
We specifically discussed what we would want to convert and what we wouldn't in context of the "yet another conversion" problem. It's a concern you raised in #62 and wasn't really addressed properly until now.
In IRC we discussed planning to convert anything with an existing access check to the generator as the profiling shows the generator is much faster than l() when access checks are involved. This was added to the issue summary but it's not hugely prominent.
We'd hold off converting anything else until we can get the doGenerate() optimised further and maybe working on the route system more, to the point where profiling brings the generator more inline with l() everywhere.
Does this make sense to you?
Comment #147
pwolanin commentedThis issue is almost ready and will potentially enhance the performance regardless of when we optimize doGenerate(): #1965074: Add cache wrapper to the UrlGenerator
Comment #147.0
pwolanin commentedadd more followups.
Comment #147.1
pwolanin commentedUpdated issue summary.
Comment #148
pwolanin commented#132: link_generator-2047619-132.patch queued for re-testing.
Comment #149
webchickAlex feels that catch's feedback here would be helpful, so assigning it to him.
Comment #150
catchWas this profiled?
Won't it break if the request object gets messed around with?
Why would we have multiple instances of the link generator knocking around, I'd expect it to be instantiated once from the container and that's it?
Comment #151
dawehnerFixed that piece of documentation.
Comment #152
pwolanin commented@catch - there are some profiles in #138 and #139. The net is that the link generator alone is slower than l() due to the greater work done in the UrlGenerator when starting from a route + parameters vs. a hard-coded system path. However, access checking a route is faster, so in many situations this would be a net speed gain.
The request object is actually not used once the active variables are set, so I'm unclear why the class is keeping a reference to it. However, the outcome would be unaffected by messing with it.
The link generator is instantiated in the container, so I don't expect there to usually be multiple instances. The comment is meant to be similar to the one in l():
Comment #153
thedavidmeister commentedI think catch is just talking about
being profiled, but this has not been profiled for this issue.
That text/logic was copied and pasted from l(), and introduced in #1922454: Gut l(), fix theme('link'), have a working framework for consistently generated links inside and outside of Twig. It may not make sense in this context.
Comment #154
pwolanin commentedtries to clarify the comment more and removes the request instance variable since it's never used.
I think storing those calculated values make sense - they are just on an instance variable and this avoids many duplicate method calls to the request object.
Comment #155
catchI got confused by 'statically cache' when they're just normal class properties, couldn't see any reason at all why they'd need to be static class properties when they're not. Storing them as separate properties is good, removing the request property even better since then it's clear that's all we need.
Comment #156
neclimdulbumping it back to RTBC then since everyone seems happy with the addressed conern.
Comment #157
catchOK. Committed/pushed to 8.x.
Needs a change notification for the API addition. I have a feeling there's an issues somewhere about caching generator stuff a bit more, but if there's no an actual dedicated issue for it, let's open one before closing this out - this is exactly the feature that if people use it will result in a lot more generator calls.
Comment #158
catchComment #159
pwolanin commentedHere's the caching issue: #1965074: Add cache wrapper to the UrlGenerator
Comment #159.0
pwolanin commentedUpdated issue summary.
Comment #160
dawehnerThank you very much. https://drupal.org/node/2078263
Comment #161
pwolanin commentedchange notice: https://drupal.org/node/2078263
Comment #162
pwolanin commentedand title....
Comment #162.0
pwolanin commentedUpdated issue summary.
Comment #163
webchickSigh. #2078287: [meta] Fix the DX of the link generator
Comment #164
thedavidmeister commentedFrom #1595614: [meta] Remove all the theme functions and templates in core that simply output a link. Replace with #type 'link' render arrays, pwolanin:
What is going on here?
I was pretty sure that we all agreed we'd only be doing #route_name conversions for links that currently have access checks, until the performance improvements for the generator land, at which point we'd look at conversion with a wider scope.
Less than 24 hours after this patch is committed and we're already on the "convert everything" train?
What changed between now and #146? Why are we abandoning the plan outlined in the issue summary?
Comment #166
tstoecklerFollow-up: #2091901: Wrong one-line description on UrlGenerator and LinkGeneratorInterface
Comment #166.0
tstoecklerUpdated issue summary.