There are at least two systems in Drupal that could really benefit from a lazier method of theming that lets the names be more arbitrary. The in core example is form API, which could utilize such a system to allow each piece of a form to be themable, automatically. Let's take an example:
function foo_form() {
$form['bar']['user']['name'] = array(
'#type' => 'textfield',
...
);
...
return $form;
}
Since all items in form api end up being a variant on theme_form_element(), if we set this up right we could automatically theme this by creating the right theming function (or template):
function theme_form__foo_form_bar_user_name($element) {
...
}
Or...
function theme_form__foo_form_bar_user($element) {
...
}
All the way down to
function theme_form__foo_form($element) {
...
}
Now, registering all of these for every form is basically impossible. We need a way to figure out which of these will work. If the theme registry could register a pattern, we can accomplish this:
(roughly)
'form_element' => array(
'arguments' => array('element' => NULL),
'pattern' => 'form__*',
'default' => 'theme_form_element',
),
The theme system would then look for anything of theme_form__* or phptemplate_form__* or whatever is appropriate (form__foo_form.tpl.php) and register that.
Now, one more thing is required. Because theme() needs to be as fast as possible, for a function like this there are two ways to approach it, I believe. We do *not* want to have to hit every pattern in the system if a theme isn't found...that's way, way too much overhead. I believe we need a way to inform the system when we're using a theme implementation that fits a pattern, and what pattern it's using (so it knows when to just use the default implementation). There are two ways to do this. one is a new theme() function, and the other is to give theme() a magic argument.
Form 1:
theme_dynamic('form__foo_form_bar_user', 'form_element', $element);
Form 2:
theme(array('form__foo_form_bar_user', 'form_element'), $element);
In form 1 it's more clear, but we're going to end up with code duplication. Form 2 sacrifices clarity for for brevity, but also adds a slight (very slight) performance hit since every theme() function must now include an is_array($hook).
I plan to work on this patch ASAP, but I welcome discussion about it before I get to coding.
Comment | File | Size | Author |
---|---|---|---|
#24 | dynamic_theme_1.patch | 9.59 KB | merlinofchaos |
#20 | dynamic_text.tar_.gz__0.txt | 869 bytes | merlinofchaos |
#19 | dynamic_theme_0.patch | 7.06 KB | merlinofchaos |
#6 | dynamic_text.tar_.gz_.txt | 10 KB | merlinofchaos |
#5 | dynamic_theme.patch | 6.82 KB | merlinofchaos |
Comments
Comment #1
dvessel CreditAttribution: dvessel commentedsubscribing
Comment #2
dvessel CreditAttribution: dvessel commentedIf anyone is confused on why this is needed in the first place (as I was).. The theme registry would have to store every single theme function. In places like theming forms or even Views where there are multitudes of functions that follow a predictable pattern, the possibility of *wildcards* can be used to prevent the registry from being overloaded.
So, I think the key thing here is to keep the theme registry on the more efficient side.
Comment #3
merlinofchaos CreditAttribution: merlinofchaos commentedAnother example that could be really useful:
Comment #4
moshe weitzman CreditAttribution: moshe weitzman commentedsubscribe
Comment #5
merlinofchaos CreditAttribution: merlinofchaos commentedFinally, here is the patch to do this.
It moves some logic out of phptemplate.engine so that all engines can have easier access to it.
Comment #6
merlinofchaos CreditAttribution: merlinofchaos commentedHere is a simple module & theme which I used to test the basic behavior.
Comment #7
merlinofchaos CreditAttribution: merlinofchaos commentedComment #8
merlinofchaos CreditAttribution: merlinofchaos commentedby the way, this patch is going to be basically necessary to port Views to 6.x.
Views relies on being able to theme each view independently. It accomplished this with a bit of code that looked to see if theme_views_view_TYPE_VIEWNAME existed, and if not backed down to theme_views_view_TYPE.
That's basically what this system does, but using registry to accomplish it via wildcards. It would be burdensome to have to register for every view, which is the other solution. THat solution could grow the theme registry cache significantly; and formapi can't even do that, as there isn't really any way to know all of the possible form combinations at hook_theme() time.
Comment #9
profix898 CreditAttribution: profix898 commentedReally nice. Subscribing.
I hope to review and test sometime this week.
Comment #10
chx CreditAttribution: chx commentedComment #11
Senpai CreditAttribution: Senpai commentedSubscribing
Comment #12
dmitrig01 CreditAttribution: dmitrig01 commentedsubscribe :) love it
Comment #13
Senpai CreditAttribution: Senpai commentedInnocent question here, but wouldn't the theme_dynamic('foo', 'form_element', $element) be far easier to keep track of in a large site with many developers working on it? Not to mention it's a more direct coding approach rather than taking the aforementioned performance hit. However slight, a method that is easier to read, doesn't involve another callback, and is generally more specific gets my vote. There's already enough modules out there that can slow sites to a crawl. Let's not add anything that could negatively affect performance if there's an alternative that's actually easier to read in plain English code.
Merlin, awesome idea here. I really like this.
Comment #14
moshe weitzman CreditAttribution: moshe weitzman commentedI setup the whole test environment for this but i honestly don't know how to verify the output versus expectations.
One bummer about this is that the 'template log' feature in devel HEAD can't learn about these candidate theme functions like it does for templates now. i would appreciate if earl looked into that. devel hooks into all theme calls by defining a phptemplate_preprocess()
Comment #15
merlinofchaos CreditAttribution: merlinofchaos commentedI picked the theme() version to do initially because theme_dynamic() will have a less efficient footprint in the end; either because it will have to duplicate a bunch of code from theme() or because it will call into theme() and then run (again) a bunch of code that's already been run.
Moshe: Interesting issue you raise. I don't have any good answers immediately. I'll have to cogitate upon it and see what comes up.
Comment #16
merlinofchaos CreditAttribution: merlinofchaos commentedMoshe: The results of the test script are largely determined by whether or not there is an override for the specific cases.
In the template.php for the theme I included, there are function overrides for 'a' and 'c' which should output 'a' and 'c' respectively.
There is a template override for 'd'.
Those are teh only overrides; anything other than 'a', 'c', or 'd' should output the results of the default theme.
Comment #17
dvessel CreditAttribution: dvessel commentedOkay, I got the wrong idea. It's not about the stored registry since every iteration of these found is stored. (did a
print var_dump(cache_get("theme_registry:test_theme", 'cache'));
. it's all in there.). It's about how these functions are called and having it fallback by looping through with $candidate. --each function in the parameter passed as an array.Just like theme() has a fallback mechanism by checking the theme, theme engine and original function. This goes in another direction so it can check each iteration of theme__a_b_c, theme_a_b, theme_a or theme__* pattern. Sorry for the confussion.
Tried it with the test files and other functions I made up. Works very well.
I've been out of it for a while. Is this still valid? If so, I'd say it's RTBC. Hope it's not too late.
Comment #18
Dries CreditAttribution: Dries commented- This looks like something that we'll want to benchmark?
- What do you mean with "May be an array, in which case the first existing hook will be used."? Extend the code comments to explain why you might want to pass in an array.
Comment #19
merlinofchaos CreditAttribution: merlinofchaos commentedAttached is new patch with better comments.
In order to benchmark this, I modified the test script to do 10,000 calls to theme() and throw away the output.
With the patch: 0.015594 sec.
Without the patch: 0.013845 sec.
Net change: For 10,000 calls to the theme function, it costs us just under 2ms. This tells me the is_array() check is very fast.
With backup it costs us a bunch more time, but this is to be expected -- we're looking to see if a theme function exists and it doesn't, so we're going to a backup.
I'll attach the new script so people can play with it. (Note that without the patch it generates a bunch of NOTICE errors; these are expected).
Comment #20
merlinofchaos CreditAttribution: merlinofchaos commentedNew test script.
Comment #21
merlinofchaos CreditAttribution: merlinofchaos commentedI think this successfully addresses both of Dries' points, so back to RTBC.
Comment #22
Dries CreditAttribution: Dries commentedThanks for benchmarking and improving the help text. Much appreciated.
The documentation is still a bit vague-ish so I'm not going to commit it yet. Maybe someone will stop by to massage the documentation some more. For example, it's probably useful to add another sentence that adds a small example of how that would work.
I see no reason to block a patch that is 99% ready, so this patch is hereby guaranteed to be committed in the next couple of days (unless a significant show-stopper comes up). This buys us a bit more time to massage the documentation (or not).
Comment #23
merlinofchaos CreditAttribution: merlinofchaos commentedI thought about an example, but that doesn't feel like something that should go on the doxygen, but rather in the developer's handbook. That said, I'm not sure about that; I have real troubles finding my way around Drupal documentation much of the time, and I think part of the reason is that there's not often a clear answer for "where should this be documented."
I will poke around at the documentation and see.
Comment #24
merlinofchaos CreditAttribution: merlinofchaos commentedThis one includes more documentation in the section on theming in general (which feels, to my mind, like a more appropriate place to put an example), as well as adding a little more to the theme() doxy itself. It also fixes a problem I noticed in the documentation with the way doxy works.
Comment #25
webchickI read over the documentation changes. While there are a couple places where things could stand to be a bit more clear, it makes a lot more sense to address this in kind of an over-arching "fix the theme documentation as a whole" effort than "overly polish one aspect of it" imo.
I'd approve of this being committed as-is (the docs here are more than adequate) and a clean-up effort for the entire theme.inc file for extra sparkly polish coming as a subsequent patch.
Comment #26
Dries CreditAttribution: Dries commentedCommitted. Thanks.
Comment #27
(not verified) CreditAttribution: commentedComment #28
drewish CreditAttribution: drewish commentedi've been puzzling over the whole pattern thing for the last few hours... i'll never complain about my patches being held up because of bad documentation ever again. if i'd come across this issue before it was committed i'd have asked for the feature to be used in at least one place in core if only for demonstration purposes.