l() and url() each have a long list of arguments that are used in various situations. When you need to set one of the last arguments, you have to include all the previous ones, which results in lovely code such $channel['link'] = url("blog/$uid", NULL, NULL, TRUE);.
This patch changes l and url to follow the following signatures:
function l($text, $path, $options = array());
function url($path = NULL, $options = array());
Options is an associative array that contains 'query', 'fragment', 'absolute', 'alias' for both, and 'html' and 'attributes' for l(). I chose this syntax because the majority of l() and url() calls consist of only $path and $text.
'Alias' is a useful new ability (which is required in chx's menu changes) where you can prevent the expensive alias lookup in url() if you already have the alias.
I converted all of core using a replacement script, though it seems to get confused somewhere in system.module and I had to manually fix 3 links. The rest of core seems to work okay though.
| Comment | File | Size | Author |
|---|---|---|---|
| #36 | refactor-l-url_3.patch | 61.19 KB | Steven |
| #24 | url_refactor_2.patch | 58.73 KB | killes@www.drop.org |
| #22 | url_refactor_1.patch | 57.88 KB | killes@www.drop.org |
| #21 | url_refactor_0.patch | 57.52 KB | killes@www.drop.org |
| #20 | url_refactor.patch | 58.05 KB | killes@www.drop.org |
Comments
Comment #1
Steven commentedHere is the updating script. Note, do NOT run twice on the same code.
It updates all .inc, .module and .php files under the current working directory (and subdirectories).
Comment #2
kkaefer commented+1 on this. The parameter definitions of
url()andl()are really annoying.I applied the patch, clicked through my Drupal installation and didn’t spot any errors on the first glance.
Comment #3
merlinofchaos commentedBig +1 on this. l() and url() syntax is highly irritating.
Comment #4
chx commentedWhat else could be said than I totally admire and love this piece of code?
Comment #5
webchickExcellent. This is just the type of change that makes the API easier and more intuitive to use.
A couple questions about the patch:
1. The PHPDoc specifies the default for absolute and alias and not for query and fragment. Should this be mentioned as well?
2. Where is 'alias' actually used in the code? I read it a couple times and couldn't figure this out.
3. $options += array(
won't that cause problems under PHP 5? I thought it should be array_merge.
Will try and test this later to confirm this.
Comment #6
kkaefer commented@webchick: afaik, += for array only adds the values in the second array to the first array if the key is not present, thus "filling up" unset values in the $options array.
Comment #7
Steven commentedwebchick: Isn't it rather obvious that these string arguments default to empty?
Also, the 'alias' property is used normally in url(), not sure why you missed it. A simple cmd-f on the patch should've brought up:
Comment #8
Steven commentedWe use + for arrays in form API too. It is faster than array_merge, because it does not renumber numerical keys.
Comment #9
dries commentedI'm OK with this patch but it's a little bit awkward to put the query string in an array.
1. It would be good to know how often we need to pass an array. If we have a pass an array 70% of the time, it might actually be harder to use?
2. url() and l() are performance critical functions. Certain pages have hundreds of URLs. I think we'll want to benchmark this patch so we can consider the performance implications (if any).
Comment #10
Steven commentedThe vast majority of these calls in core only has the 2 parameters (roughly 75%). For the other calls, there is no real preference for any specific parameter. We often pass only a fragment, only a query, only attrbutes, etc. The new syntax is much easier to remember and read in this case, rather than having to remember and count the arguments, you can just tell by the array key.
Performance-wise, someone else will have to benchmark.
Comment #11
ianivs commentedAnother option that would be useful is 'protocol'. Right now, if I want to create a bunch of https links I have to process them after l(). There are many other situations where using something other than 'http' is needed.
Like chx said: "and without this patch, adding another parameter [to l()] is crazy". So it may be a good time to think about it.
Comment #12
rkerr commentedSweet.
If we can get "scheme" or "protocol" support I'll +1 :)
Also, it's good keeping query string values in an array as it gives us a hope of transitioning to "suggested" practice of using ; instead of & as a separator without worrying module developers.
Comment #13
andremolnar commentedFor every second saved by not having to type a couple of parameters I lose a minute looking up what possible values could go into that array and in what format they are expected. This is of course because I use an IDE that tells me what arguments are expected when I start typing a function name. And $options = array is just about useless to people as a hint for what they should include next.
But nobody cares about the IDE users - they're obviously not hard core.
Why not just order the arguments from most commonly used to least commonly used.
Comment #14
Steven commentedThe point about ordering is that the arguments are each used as frequently as eachother. There is no optimal order.
Instead of seeing:
url("user/$account->uid", NULL, NULL, TRUE)
you get:
url("user/$account->uid", array('absolute' => TRUE)).
I don't need to figure out what the TRUE is, I can see it immediately. IDE or not. The available values to l() and url() are pretty logical, as you are dealing with a URL (query, fragment, absolute, ...) and the functionality of these functions remains the same as before.
Updated patch attached:
Patch includes the l/url() change as well as the result of applying the replace.php script to core.
Comment #15
Steven commentedUpdated replacement script. Do NOT run twice on the same code.
Comment #16
Steven commentedAdding in scheme is not so easy, because it would only work for absolute=>TRUE and we'd have to rewrite the base_url to do it. It might slow down the patch. However, if we do add it, it would be available for all l(), url() calls, as well as anything added via hook_links(). This array notation is quite flexible and can bubble through all of Drupal.
I forgot some debug code though, and removed the duplicate adding of default values between l() and url(). New patch attached.
Comment #17
webchickPatch no longer applies.
Comment #18
Steven commentedKilles did some testing with a 5.0 version of this patch, and reported a speed-up of roughly 10-15% (according to m3avrck).
This may sound weird given the added complexity, but we are passing in a lot less arguments every time. Each call to the old l() had to fill in up to 5 default values for arguments (+ 2 required), and passed 4 of them to url(). With the new code, there is only 1 default value and it is filled in in one go (with the += operator). Also, the variables for url() are only filled in inside url() and do not have to be passed from l() to url().
This makes sense if you consider that the majority of all calls to l() and url() don't use any additional arguments (simple links, with no query/fragment/etc), so most calls had a lot of overhead before to fill in those arguments and pass them along needlessly.
I trust killes' benchmarking fu and though I am surprised at the result, it would not be the first time that we see something like this in PHP.
Rerolled for HEAD.
Comment #19
dries commentedOn my benchmark setup, I observe a minor performance degradation. Generating the main page is, on average, 0.4% slower. Pretty insignificant so that's a good thing.
Would still like to know what killes/maverck did to get these results?
Comment #20
killes@www.drop.org commentedpatch re-rolled
Comment #21
killes@www.drop.org commentedoops...
Comment #22
killes@www.drop.org commentedthere was one link in the commment form not converted.
Comment #23
killes@www.drop.org commentedUpdated patch.
Comment #24
killes@www.drop.org commentedthe patch...
Comment #25
killes@www.drop.org commentedI don't know why Steven thought I had done a benchmark. I had wanted to, but actually did not. But I have now. The result is:
With patch:
374+-7 s
Without patch
365+-5 s
That is, the patch makes my test take about 2.5 % longer.
Comment #26
m3avrck commentedHmmm, I must have gotten something backwards, but I could have sworn that killes said he did benchmarking and it was faster, mentioned in IRC... that's when we were all scratching our heads as to why that was possible.
Perhaps that was a fluke, doh!
Comment #27
moonray commentedIf we had all our links in drupal in one consistent format, that we could just drop into url() or l() or another similar function it would make modding much easier. (see 191650)
It would be nice to be able to drop such an array, as gotten from the links used in hook_link() and hook_link_alter(), into one of these functions. But perhaps a new function is needed instead?
Thoughts?
Comment #28
chx commented2.5% -- but let's not forget that now we can pre-store aliased links and show those. This would provide a much bigger win than 2.5% . Of course, we could just add an 8th parameter to l... but I would really prefer not.
Comment #29
dries commented2.5% -- but let's not forget that now we can pre-store aliased links and show those. This would provide a much bigger win than 2.5%
Agreed. We pay some, but we might amortize that cost by being able to pass path aliases into l().
However, I'd like to see proof of that. Are we sure we'll get that 2.5% back? How are we going to do that? This patch would be a no-brainer if someone extended this patch to do exactly that. Until then, we'll have to assume that this patch degrades performance a little bit ...
In other words, bonus point if someone can take advantage of this patch to show us that it is the right thing to do. :)
Comment #30
Steven commentedAFAIK it's been shown before that preloading the alias can have a significant speed up for heavily aliased sites. Regardless, the addition of this check if just one 'if' statement in what is already a complicated function. I doubt the alias feature would cause any changes in performance.
Everyone agrees the new format is easier to use and the performance hit turns out to be not that bad. The fact that we now have a unified link format to use across Drupal is good too and it makes code more readable.
Can this please be committed?
Comment #31
dries commentedEveryone agrees the new format is easier to use and the performance hit turns out to be not that bad.
No, some people voted against this patch because it doesn't play nice with IDEs that have auto-completion.
AFAIK it's been shown before that preloading the alias can have a significant speed up for heavily aliased sites.
Thus, to make a long story short: the key advantage of this patch is the 'alias' option IMO. I'd like to know that it actually does buy us something -- we have no proof of that. In fact, Gerhard tried to take advantage of this patch to remove some alias lookups but it didn't buy us anything.
Comment #32
Steven commentedUm, the IDE argument is not valid IMO. The new syntax is so simple, you don't need autocompletion anymore. Plus, autocompletion only helps when writing code. It does not help when reading code.
As for the 'alias' thing, this patch was created because adding another '$alias' parameter to l() and url() was the drop that spilled the bucket. But even without 'alias', this patch has a lot of merit... the alias feature is only a single if statement that can easily be removed.
Comment #33
chx commentedthe menu system will use alias. that could save quite a number of queries. It was raised more than once that we could store links for a node... all the possibilities :)
Comment #34
Jaza commentedSome issues with the doxygen comments in this patch:
The comment in the current code says nothing about an array of key/value properties. Neither the current nor the patched code caters to an array of key/value properties. Also, I think that accepting two different formats (string or array) would be a bad idea, as it would only make for inconsistency and for a messier API. Plus, I can't see any code in core that actually passes in an array for 'query' (hardly surprising if url() can't actually handle it!).
Other than that, looks good to me. If the benchmarks show a 2.5% performance hit, that's acceptable IMO, considering the improved readability/writability of code that this patch brings in.
Comment #35
dries commentedAlright. Let's get the documentation fixed, and then it's ready to go.
Comment #36
Steven commentedAck, the array query syntax got lost in #16... I must have messed up the merge.
I disagree that this syntax is messy: it follows with other Drupal practices like passing MIME headers in as arrays or keeping links in a structured format rather than an HTML string. Plus, this is important because often people forget to call urlencode() on URL arguments, which can lead to bugs, header spoofing or even XSS problems. By passing in an array, Drupal automatically urlencodes for you.
(note that the old syntax still works and has some uses too, but the other method is recommended when you control the entire query string).
I rerolled the patch with the query array syntax and added the note about not including '#' for fragments.
The examples I can find in core don't benefit from the new syntax, as they either just use drupal_get_destination() or append a piece of existing query string. For the latter, we could use drupal_query_string_encode() manually instead of building the string directly.
That doesn't mean it can't be used to clean up contrib modules though. Plenty of them use straight GET arguments. If we're going to clean up l() and url(), we might as well remove all icky bits. Query arguments are structurally key/value pairs, and we should support handling this data in the most natural form (an array) rather than only in serialized strings.
Comment #37
webchickI kind of lean toward ditching the old syntax altogether and just using arrays. You're right; it is intuitive that way.
Having to manually escape arguments seems like a dangerous thing to trust developers to do. But you said that there were use cases for this?
Comment #38
webchickSigh. I need to not respond to issues before noon. ;)
Comment #39
dries commentedCommitted to CVS HEAD. Marking this 'active' until it has been properly documented in the upgrade instructions. :)
Comment #40
syscrusher commentedNice work, Dries! I use these functions a lot in the Links Package (contrib module set). You've caused me to need to rework a little of my code, but I'm not complaining because I think this is a good change to the API. This will make it much more intuitive for future development, because we don't have to remember the order of all those extra parameters since the array is associative.
I know it's kind of after-the-fact, but I'm giving it a +1 anyway just to say thanks!
Syscrusher
Comment #41
dwwhttp://drupal.org/node/114774#url
Comment #42
(not verified) commented