Allow more granular theming of drupal_render'ed elements

yched - May 7, 2009 - 01:10
Project:Drupal
Version:7.x-dev
Component:node system
Category:task
Priority:normal
Assigned:moshe weitzman
Status:closed
Issue tags:Favorite-of-Dries, Needs design review, Needs Documentation, RDF
Description

Long standing issue with the all-inclusive $content variable in node templates. It keeps base node templates simple (print $content; 'just works'), but:
- if you want to a more granular layout ("put this field on top, then that image floated left"), you have to manually theme each component individually from the $node raw values. Lots of potential security issues in the hands of themers.
- then, you cannot use $content to say "display the rest floated to the right of the image", because you will get duplicate output for the fields you already displayed. You need eat your field-by-field manual display up to the end, and your templates will miss later node additions.

(Using the word 'field' above in the generic sense - this is true for all node additions. Field API extends the issue to all fieldable entities, though.)

CCK D6 used ugly hacks around the drupal_render() workflow to a) grab the rendered fields and expose them as separate template variables b) (worse) have a way to specify that they should be 'excluded' from the $content variable. This made node themeing a fair bit easier, but the hacks are nonetheless ugly, and they are now in core :-(, and I don't think we whould ship with this. And they only "work" for field additions.

The root of the issue is that whatever a theme wants to do, it comes after $node->content being already rendered by theme.inc's template_preprocess_node(). Nothing can be done of $node->content subparts because #printed = TRUE.

Possible ways out of this:

1) Move the $variables['content'] = drupal_render($node->content); line out of template_preprocess_node(), and let each theme do it - or something more complex instead, like $foo =  drupal_render($node->content['foo']); $bar = ...; $content = drupal_render($node->content); if they need to, in their own node preprocessor.
Drawback: doesn't play nice with stark, or other CSS-only themes, since it forces themes to implement a preprocessor for each fieldable entity. So, probably a no go :-(

2) bring drupal_render() into the templates. The l(), t() functions are already "allowed" in templates. We might add r() as a shortcut to drupal_render() - or rename the function altogether. You can then do <?php print r($content['foo']); ?> here, <?php print r($content['bar']); ?> there, and <?php print r($content); ?> to get 'the rest'.
Drawbacks:
- Adds some PHP for themers.
Re: Fairly simple, though, and the base node template would already ship with <?php print r($content); ?>
- Adds a step of discovery ('what do I have in the $content array... ?').
Re: This is not really different from the current situation ('what 3rd-party-added variables do I have in the template ?'), and doing a print_r($content) is easier than print_r(get_defined_vars())
- Brings raw, unsafe stuff in the hands of themers.
Re: themers already have $node, $field_name (raw field values)... The fact that granular layout requires manually displaying the needed bits forces them do much more dangerous things with raw values right now...
- You have to figure out the render order : r($content['foo']) == '' if r($content) was called first in your template.
Re: right, I see no way around that - that would require moving back to solution 1) (render in preprocess)
At worst, you have to render you 'parts' in advance at the top of the template.

#1

yched - May 7, 2009 - 01:23

Pushing 2) a little further, Eaton suggests a p() (for 'print') function providing a more generic shortcut :

<?php
function p($var) {
  if (
is_array($var)) {
    print
drupal_render($var);
  }
  else {
    print
$var;
  }
?>

Everywhere you did <?php print $var; ?>, you'd now do <?php p($var); ?>

Thus we don't really 'add more PHP for themers', since p() would be the one and only template print construct.

#2

eaton - May 7, 2009 - 01:20

I'm not *extremely* happy about something like p(), but it's the only solution I can see that allows us to add more intelligence without putting a big burden on themers (managing security themselves, etc). There are some tricky subtle bits, too: would this make it possible to create the following?

$vars['content'] = $content;
$vars['field_1'] &= $content['field_1'];

p($field_1);
p($content);

Or does the variable unpacking process break references? Hmmmm.

#3

merlinofchaos - May 7, 2009 - 03:26

Or does the variable unpacking process break references? Hmmmm.

Yes it does.

Option 2 also does not let us do:

<?php p($content['foo']); ?>
<?php p($content); ?>
<?php p($content['bar']); ?>

Because 'bar' will already have been printed. In fact, there is no way to do this with PHPTemplate at all because it is a single pass system. And yet, we're going to have a lot of problems with this. You'll end up having to do stuff like this:

<?php
 
// Prerender a lot of stuff so we can place it later
 
$foo = p($content['foo']);
 
$bar = p($content['bar']);
?>

<div>
<?php p($foo); ?>
<?php p($content); ?>
<?php p($bar); ?>
</div>

Which I guess is OK but won't make templatemakers happy.

#4

yched - May 7, 2009 - 11:47

Yes, that's the last 'drawback' I mentioned in the OP. I see no workaround for this, it's the price to pay for the 'print the rest (including the fields I don't want to know about or that were not there yet when I wrote the template)' feature - which is an essential facilitator.
And I still think that it should make templatemakers much happier than what they currently have to do to have 'granular themeing' : manual theming, that require them to play dangerous games with raw data (or make them depend on a theme-aware PHP dev who writes safe preprocessors for them)

#5

catch - May 12, 2009 - 18:00

#3 seems like a pretty small price to pay in the scheme of things and better than what we have now.

I quite like p($var); - easy to remember, makes templates very concise.

It also brings us closer to a situation where we might be able to avoid waste like #451156: Duplicate calls to format_date() in node rendering maybe?

#6

yched - May 12, 2009 - 18:07

The only drawback with p() is that it needs to take its argument by reference, so that $content['foo']['#printed'] == TRUE after p($content['foo']).
But then you can't do p('string literal'); which defeats the "forget 'print', use 'p()' everywhere" part.

#7

sun - May 12, 2009 - 18:54

Very interesting.

/me tinkers

#8

sun - May 12, 2009 - 18:56

Forgot to note what I mentioned in IRC: We want both p() and r(), because you often want to render a single field and use that inside l() or whatever.

#9

merlinofchaos - May 12, 2009 - 19:08

References would become a nonissue if we used objects instead of arrays. But saying that might get me lynched 'round these parts.

#10

yched - May 12, 2009 - 21:45
Status:active» needs review

Yep, moving all render structures to nested ArrayObject objects feels daunting.

Attached patch takes a minimal approach, by moving the $content variable to ArrayObject($node->content) in node_preprocess. This only adds an object wrapper around the toplevel array. drupal_render() still operates on an array. Not too elegant, but does the job.

Just for testing purpose, the patch adds two $node->content['test'] and $node->content['test2'] elements, converts garland's node.tpl.php to use p() and renders the two test elements separately.

AttachmentSizeStatusTest resultOperations
granular_template-455844-10.patch7.52 KBIdleFailed: Failed to apply patch.View details

#11

yched - May 12, 2009 - 21:49

Without witespace fixes.

AttachmentSizeStatusTest resultOperations
granular_template-455844-11.patch3.85 KBIdleFailed: 11230 passes, 1 fail, 1 exceptionView details

#12

System Message - May 12, 2009 - 22:00
Status:needs review» needs work

The last submitted patch failed testing.

#13

merlinofchaos - May 13, 2009 - 15:20

yched, what actually happens with this:

+    <?php p($content, 'test'); ?>
+    <?php p($content); ?>
+    <?php p($content, 'test2'); ?>

#14

yched - May 13, 2009 - 21:40

The idea is that if we want a p() function that doesn't take its arg by reference (and thus allows p('literal')), then we always need to provide the full $content ArrayObject, not $content['foo'] because then the #printed = TRUE bits wouldn't bubble up in $content.

The test code in the node template produces:

<?php p($content, 'test');  // Prints 'foo' ?>
<?php p($content);          // Prints the regular node content + no 'foo' ('test' already rendered) + 'bar' ?>
<?php p($content, 'test2'); // Prints nothing ('test2' already rendered) ?>

#15

yched - May 13, 2009 - 21:40
Status:needs work» needs review

The test failure was caused by the original modules/node/node.tpl.php not converted - broke stark theme.
Attached patch fixes this.

AttachmentSizeStatusTest resultOperations
granular_template-455844-15.patch5.42 KBIdleFailed: Failed to apply patch.View details

#16

Frando - May 14, 2009 - 01:38

Subscribe. Very intersting stuff. Dealing with drupal_render() in the theme context is an important task as we're moving more and more towards using drupal_render() as much as possible, which is a great thing. I was thinking about these issues quite a bit, and I generally agree with the approaches taken here - both regarding a p() or r() function to be used in templates, and about using ArrayObjects to always copy on reference. Will review more closely as soon as time permits.

#17

yched - May 14, 2009 - 19:17

Heh, took me 15 followups to realize that the issue with p() as a richer variant of print is that it specifically forbids the code merlinofchaos posted in #3 :

<?php
 
// Prerender some stuff so we can place it later
 
$bar = p($content, 'bar'); // Doesn't work : p() prints but doesn't return anything...
?>

<div>
<?php p($content, 'foo'); ?>
<?php p($content); ?>
<?php p($bar); ?>
</div>

which is the only way to display "everything but $content['bar'], then $content['bar'] after that".

Two proposals :

1) If we want to keep p() (which does have a nice unifying feeling), it needs an optional 'return instead of print' boolean param, so the above would become :

<?php
  $bar
= p($content, 'bar', TRUE);
?>

<div>
<?php p($content, 'foo'); ?>
<?php p($content); ?>
<?php p($bar); ?>
</div>

Question : are we OK with exposing those subtleties for themers ? As far as I'm concerned, why not.

2) Forget p() and just introduce r(), as a simple alias for drupal_render(). That's going back to the proposal 2) in my OP.
r() doesn't need to accept string literals, so it can take its arg by ref, no more ArrayObject dance. The example becomes:

<?php
  $bar
= r($content['bar']);
?>

<div>
<?php print r($content['foo']); ?>
<?php print r($content); ?>
<?php print $bar; ?>
</div>

This allows more advanced stuff like r($content['bar']['baz']), while the p($content, $key) syntax doesn't let you access nested elements.
Attached patch implements this second proposal. If we chose to go that way, keeping both drupal_render() and r() functions or only one of them can be debated...

Note that in both cases, the 'prerender' part can still be done in the theme preprocessor for cleaner templates, which slightly mitigates the 'more PHP for themers' drawback of solution 1).

Both directions are valid IMO, and both are far better than what we currently have, so it's up to us to decide...

AttachmentSizeStatusTest resultOperations
granular_template-455844-17.patch2.68 KBIdleFailed: Failed to apply patch.View details

#18

sun - May 14, 2009 - 20:18

How about solving the theme-me-that-multiple-times and i-want-to-output-in-weird-order issues this way?

<?php
/**
* Shortcut for drupal_render(), to be used in templates.
*
* @param $force
*   (Optional) Whether to force rendering of the passed in element. Set to
*   TRUE to render elements multiple times.
*/
function r(&$element, $force = FALSE) {
  if (
$force) {
   
$element['#printed'] = NULL;
  }
  return
drupal_render($element);
}
?>

Aside from that, we are talking about micro-optimizations of a perfect world. People need much more PHP code in their templates to achieve a customized output currently. A simple r() function that themes entity elements properly is a big step towards that perfect world.

#19

moshe weitzman - May 14, 2009 - 20:27

I really like the most recent proposal for r($content['bar']['baz']). More flexibility for themers is the primary motivation here. Also attractive is the performance gain. We will only be theming the bits that the template actually uses. This is pretty much the holy grail "pull system" that we've been needing. If you benchmark D7 now, you see that drupal_render() and theme() are tops in terms of costliness. This patch can reduce calls to both significantly.

I would like to go even further and have templates inform the nodeapi('view') functions about their needed vars so that only the needed parts are built. But thats a much larger project. And I have no great idea how to do it. Maybe we extend hook_theme() for templates so they advertise their needed vars.

I think we should rename drupal_render() to r(), but don't care too much.

#20

catch - May 14, 2009 - 20:45

Given yched's explanation, r() seems like a really good option. Also happy with renaming drupal_render() (unless sun's $force param turns out to be a theme specific need which we wouldn't want there otherwise).

#21

yched - May 14, 2009 - 21:19

After IRC chat with sun, it seems that 'output some piece multiple times by adding a $force_print_even_if_#printed_is_TRUE param for drupal_render()' would require some changes in drupal_render() to use the existing $element['#children'] property if one is already there, instead of recomputing it all the way down. Maybe it's trivial, maybe it has nasty consequences, I'm not that familiar with the D7 drupal_render refactors.

Also, note that the current approach in #17 option 2) lets you 'save' $foo = r($content['foo']); and print it in several places.
What it doesn't let you do is : display $content['foo'] both at it's 'regular' position inside the all-inclusive r($content) *and also* some place else.
I can imagine use cases for that, but it seems a bit specific.

So +1 for the idea, but I would vote for keeping it for a later patch.

#22

yched - May 14, 2009 - 21:29

Well, maybe it could simply be added to the r() wrapper :

function r(&$element, $force = FALSE) {
  if ($force && !empty($element['#children'])) {
    $prefix = isset($element['#prefix']) ? $element['#prefix'] : '';
    $suffix = isset($element['#suffix']) ? $element['#suffix'] : '';
    return $prefix . $element['#children'] . $suffix;
  }
  return drupal_render($element);
}

I'm not sure right now whether it's a fine solution or an ugly hack ;-)

[edit: as catch points, I guess the answer depends on whether we think that this $force feature belongs to drupal_render() itself. It seems the general opinion is that we shouldn't keep both r() and drupal_render() - unless this gives us some reason to so]

#23

yched - May 15, 2009 - 22:26

On further thought, I see no reason why the 'force reprint' feature discussed in #18 and #21-22, if deemed useful, shouldn't belong to the regular drupal_render() function. At worst, it's an additional $force param defaulting to FALSE, pretty harmless. So :
- we don't need to decide whether we want it or not before we can merge r() and drupal_render()
- I'd still vote to discuss and code it in a followup patch

I won't embark in renaming drupal_render() to r() just yet. Would make a huge patch that gets outdated every 2 hours. I'll do so when either Dries or Angie approve the patch and the rename, and state something like 'this patch will get in as soon as it includes the drupal_render() / r() rename".

Similarly, the nice cleanup that this patch allows around field API's field_attach_preprocess() and $field['exclude'] will be done in a followup patch, to keep this one clean and simple.

Attached patch removes the 'demonstration' test code, and updates the theming of the other fieldable core entity: user-profile.tpl.php and preprocessor (which used its own special trick to allow finer theming control in the template).
Still TODO: update the variables docs in node.tpl.php and user-profile.tpl.php, no time or braincells for this tonight.

AttachmentSizeStatusTest resultOperations
granular_template-455844-23.patch6.47 KBIdleFailed: Failed to apply patch.View details

#24

yched - May 15, 2009 - 22:30

Sigh. Without whitespace fixes.

AttachmentSizeStatusTest resultOperations
granular_template-455844-24.patch4.53 KBIdleFailed: Failed to apply patch.View details

#25

Benjamin Melançon - May 16, 2009 - 03:04

This could be quite relevant for adding RDFa to Drupal elements in a consistent and clean manner.

#26

System Message - May 22, 2009 - 12:35
Status:needs review» needs work

The last submitted patch failed testing.

#27

yched - May 22, 2009 - 13:57
Status:needs work» needs review

Rerolled after #363905: move template_preproces_node/block out of theme.inc.

To summarize: This now needs an approval from Dries or webchick before we can move forward.
I'm tempted to mark this as 'critical', since it's a requirement to allow flexible theming of nodes and of any other 'compound' entity (fieldable or not)
It will also (in a followup patch) let us get rid of some ugly Field API code and concepts, inherited from CCK's workarounds to allow flexible theming of fields while being contrib.

What the patch does (see OP, #17, #19 for rationale) :
- defer drupal_render($node->content) and drupal_render($user->content) to templates instead of preprocessors, allowing granular rendering and layout of individual bits and of 'the rest'.
- introduce an easy-for-themers r() function (currently an alias of drupal_render) and use it so that default node and user-profile templates keep their current out-of-the-box behavior.

Still TODO when we get a green light:
- PHPdoc: update the description of the variables in templates
- actually rename drupal_render() to r(). When we start doing so, the patch will inflate and get outdated twice a day, so I'd prefer to get a formal approval first.

AttachmentSizeStatusTest resultOperations
granular_template-455844-27.patch4.93 KBIdleFailed: Failed to apply patch.View details

#28

System Message - May 25, 2009 - 17:06
Status:needs review» needs work

The last submitted patch failed testing.

#29

yched - May 26, 2009 - 23:22

Reroll, + bump.

AttachmentSizeStatusTest resultOperations
granular_template-455844-29.patch4.7 KBIdleFailed: Failed to install HEAD.View details

#30

catch - May 26, 2009 - 23:30
Status:needs work» needs review

#31

moshe weitzman - May 27, 2009 - 02:28
Status:needs review» reviewed & tested by the community

Wow - finally looked at the diff and this is really straightforward. I think it is the way forward. I'm going to mark this RTBC so we can get some committer feedback. Committers should mark this CNW when replying. The two remaining TODOs at bottom of #27 are known issues.

Spotted one micro bug:

  1. typo: descritption

#32

webchick - May 27, 2009 - 02:43
Status:reviewed & tested by the community» needs review

So in this issue I see a lot of ycheds, moshes, and catches, but I don't see a lot of John Albins, geerlingguys, and emmajanes.

If we're trying to make this easier for themers, let's ask some themers for their thoughts and make sure one of them marks this RTBC. ;)

#33

webchick - May 27, 2009 - 03:06
Issue tags:+needs themer review

Tagging.

#34

emmajane - May 27, 2009 - 03:52
Issue tags:-needs themer review

Seeing as my name has been invoked, I would just like to say that I like my bike sheds blue. ;) Seriously though: this is a really interesting patch. Incredibly simple and yet it does seem to put a lot more control into the hands of themers. Here are my comments:

(1) print r() and print_r() are too "close." I would never want to have to teach a class where I have to explain the difference between the two. I understand that "render" is a scary word, but "r" on its own is not descriptive enough to easily distinguish it from its unrelated cousin print_r. I can hardly recommend the word "extract" being shortened to e() without looking like the world's most vain person, but I would like to encourage people to do a little thinking about the potential problem of using print r().

(2) Will I really have to wrap all my variables in yarrr()? That means TWO functions (print and r()) to print a variable. Ugh. Annoying. On the other hand: the ability to print out "leftovers" is awesome. On this point I think the control that I get as a themer outweighs the twitch it's giving me. Especially if it allows me to extract the "fields" I want to customize the display of and then print the remainder of the fields.

If #1 can be "fixed" then I can ignore #2 and will approve of the change.

#35

Jacine - May 27, 2009 - 05:10

print r() and print_r() being very close was my initial thought, but I can't think of a better alternative, and I'll get over it. ;) I barely use print_r() anymore since I can use dsm() with Devel anyway.

Being able to print leftovers in $content without jumping through hoops would be a very exciting and much appreciated improvement.

#36

moshe weitzman - May 27, 2009 - 05:14

thanks @emmajane.

I had a similar reaction to print r() - I kept seeing print_r(). So, lets get back to the motivation of r(). We are using single letter function name in order to avoid clutterring the templates with too much php. I think thats pretty important, so lets keep the brevity. I could also live with dr() which we keep as a wrapper for drupal_render(). That meets the brevity goal and is dissimilar enough with print_r(). The attached patch uses dr().

Attached also adds and polishes the docs in the tpl.php files. Specifically, there are instructions for viewing the available variables in the template and instructions specific to 'print the rest'. I think these are improvements, though we could drop them if others disagree.

@webchick - yes we do want to help themers with this patch. in addition, there are significant performance improvements here which will be realized as people customize their nodes and profiles (less rendering/theming). and code cleanliness improvements will come with follow-up fields/cck patches.

AttachmentSizeStatusTest resultOperations
dr.patch7.47 KBIdleFailed: Failed to install HEAD.View details

#37

catch - May 27, 2009 - 08:50

print r() vs. print_r() is a good reason not to use r(). I can live with dr() too.

#38

victorkane - May 27, 2009 - 09:04

Agree, just a couple of things:

1. It must be possible for end users not to have to do anything.
2. Since the $node content will be available on the page template, there is a slight risk that people new to Drupal theming will try to theme the node in the page template, thus blurring the difference. But since a lot of theming work over the years has been dedicated to getting at the structure in the already rendered $content, the exposed r() is wonderful.

Victor Kane
http://awebfactory.com.ar
http://projectflowandtracker.com

#39

Benjamin Melançon - May 27, 2009 - 10:05

Instead of only wrapping drupal_render – considering that the short function is meant for use in templates – shouldn't we include the print statement right in there?

<?php
/**
* Print output of drupal_render(), to be used in templates.
*/
function dr(&$element) {
  print
drupal_render($element);
}
?>

We could even go back to r(), or use d() or p(), and grab that coveted one-letter-function real estate, heh.

The use in a template would be very simple:

<div class="example">
<?php
  dr
($node->element);
?>

</div>
<?php
  dr
($node);
?>

#40

adrinux - May 27, 2009 - 10:47

Very exciting patch overall.
I second the change from r() to dr(), r really would line us up for some frustration.
There does seem to be a lot more visual clutter in

<?php print dr($content); ?>

as opposed to
<?php print $content ?>

But then a lot less clutter when you start pulling apart $content to theme individual fields :)

Benjamin's suggestion would cure that, if it's workable.

#41

catch - May 27, 2009 - 11:32

Ben's suggestion takes us back to yched's #1 - which was p($var) - except calling it dr($var) and not using it for printing strings (which would save messing about references. The issue there is the example in #17 where you want to pre-render something first then print it later. I'm not sure why we could just use drupal_render() directly for that case though (and probably in a pre-process).

#42

eigentor - May 27, 2009 - 12:14

Very exciting.
Don't have much of a clue of the technical side.

But second any other naming than print r
Make it print dr print vr print cr whatever.

Especially not-so-experienced coders would think: "ah, I forgot the underscore, let's put that in..."

#43

sun - May 27, 2009 - 12:27

As already mentioned in #8 and #21, themers often want to use the output for something else. Maybe an example clarifies it better:

<?php
// Render greeting label.
$labeltext = dr($content['field_label_text']); // This is a single textfield value.

print theme('image', path_to_theme() . '/images/foo.jpg', $labeltext);
?>

Maybe this example isn't so good. Anyway, I hope you get the point.

#44

moshe weitzman - May 27, 2009 - 12:31

This patch converts page.tpl.php as well.

AttachmentSizeStatusTest resultOperations
dr.patch14.27 KBIdleFailed: 11447 passes, 1 fail, 0 exceptionsView details

#45

catch - May 27, 2009 - 12:32

So... keep dr() as a wrapper around drupal_render(), possibly renaming drupal_render() to dr() later. Then add p() which prints the output of drupal_render() so that in 90% of cases you do

<?php
p
($content);
?>

<?php
// Render greeting label.
$labeltext = dr($content['field_label_text']); // This is a single textfield value.

p($content);

print
theme('image', path_to_theme() . '/images/foo.jpg', $labeltext);
?>

Something like that?

#46

System Message - May 27, 2009 - 12:46
Status:needs review» needs work

The last submitted patch failed testing.

#47

yched - May 27, 2009 - 13:06
Status:needs work» needs review

Wow, great to see so much feedback overnight :-). Thanks moshe for the updates.

About print dr($var) being tedious and the need for p($var) as a shortcut : I'm personally not really convinced. I found p() interesting as a generic, one-fits-all replacement for print, but the discussion above (#6, #17) showed it is difficult to attain.
If we cannot do this, then I think p() as proposed in #45 gives minor syntactic sugar but mainly brings confusion. I'm not sure having themers figure out 'when should I use an explicit print, when should I not' (as in the code sample in #45) is a real gain.

[edit: PS - autocompletion test fails ?? WTF ?]

#48

webchick - May 27, 2009 - 14:05

I agree with yched. I would like to keep the number of new Drupalisms to learn here to a minimum. #45 makes me cry out in agony as it's not at all clear what's going on, and I forsee people getting terribly confused between p() and r() and when to use them vs calling a theme function directly.

RADICAL idea: Use a short, descriptive actual word rather than an abbreviation. Perhaps something like output() and render() (but maybe better words than that). Then it would be a lot more clear from reading the code what's going on. Not sure if the "pain to type" factor would outweigh the "not making Drupal theming harder to learn that it already is" factor, but I'm open to suggestions/critique.

#49

ghankstef - May 27, 2009 - 14:27

I am a themer who only chimes in on these discussions occasionally.

"References would become a nonissue if we used objects instead of arrays. But saying that might get me lynched 'round these parts." - merlinofchaos

What would it be like to create a parallel "track" of object based theming along side arrays. The hardest part of learning theming for me was to learn to use nested arrays and pick the right "fields" out of a nested array:

var_dump($node); // or
dsm($node); //

The first time I had to do

print $node->content['body']['#value'];

I had a hard time getting my head around it. I think this is the most difficult part of theming at this point (assuming you bring decent CSS and JS skills to the table). I'm used to nested arrays now but the are still really hairy at times - especially with node reference fields. Node reference arrays get nested arrays in nested arrays - not real themer friendly.

I remember back in the day when I did some Cold Fusion they had a release where you could use use alternate procedural and object oriented techniques to accomplish the same tasks. What would it be like to do something similar in Drupal 7 theming. It may be too big of a task to accomplish by 9/1/2009 but what would it take? Would it be an improvement?

Regardless, the combination of fields in core and the ability to make $content work more like the form api where we can pick out fields and then just get the rest without duplication is a great step forward.

#50

redndahead - May 27, 2009 - 15:52

How about display(). I think that's more descriptive.

#51

moshe weitzman - May 27, 2009 - 16:11

That autocomplete test failure was very helpful! We can't convert page.tpl.php in this patch. The problem is that $scripts, $styles and $head are all created during preprocess but rendering of $content happens later, during page.tpl.php. The problem is that #autocomplete_path and #attached_css have yet to add their css and js. This is a nasty chicken egg problem.

So, lets proceed with #36 and forget about the most recent patch in #44.

#52

yched - May 28, 2009 - 14:26

re #48 / #50 : output() / render() / display() / other_name()...
I guess I'd vote for render(). A little more technical than the other suggestions maybe, but :
- at least it shows there's something special about $content. As a themer, why should I 'display' $content but not 'display' $title ?
- it keeps a better connection to the actual internal structure and the way it's referred to in all our comments, docs, etc ('render', 'a render array'...), and thus makes an easier transition when themers need to take a few steps in drupal API territory (let's face it, it will still happen even after this patch...)
- having render() either as an alias or a replacement of drupal_render() both keep consistency in our APIs. IMO any other arbitrary name will be wrong on that regard : output() as an alias for drupal_render() feels awkward, and renaming drupal_render() to output() breaks all our docs (we don't want to have to talk about 'output arrays', do we ?)

r() would have been the perfect match next to t(), l(), but agreed that print_r() / print r() is unfortunate.

#53

JohnAlbin - May 28, 2009 - 16:45
Status:needs review» needs work

So, I'm noticing that with this current patch, we would still have the problem that in page.tpl.php some variables would require print dr($content); while other variables would just be print $title;. On the face of it, that seems likely to confuse the hell out of themers.

However, perhaps I have a solution to the above problem, the what-the-heck-do-we-name-this-function issue, and to the chicken-egg rendering problem. #306358: Add $classes to templates with new theme_process_hook functions was committed 5 seconds ago (seriously! http://drupal.org/cvs?commit=217598) and just added a new THEME_process_HOOK phase to template variable processing.

So to briefly describe what that issue introduced, the order of operations for variables on templates in D7 now is: (I'm using node as an example and skipping some possible hooks, like MODULE hooks, to reduce noise; see #306358 for full details.)

  1. template_preprocess_node()
  2. THEME_preprocess_node()
  3. template_process_node()
  4. THEME_process_node()
  5. node.tpl.php

Right now, most variables are rendered in the template_preprocess_node phase (or earlier). The current patch idea above would move the rendering to the node.tpl.php via a dr() function (any body else see that as a “Doctor” function?).

But how about we move the rendering to template_process_node()? That's actually the explicit purpose of the new "process" variables phase; right now, we only render a $classes variable for use in the wrapping div of the template, but I fully intended to expand that idea to other places. And, to me, this seems like a really good place to use it.

Let me give a concrete example (I'm going to use a generic render() function; we can talk naming later):

  1. pass an unrendered content array to template_preprocess_node() and call it $vars['node'].
  2. in THEME_preprocess_node(), allow themes to render any pieces of $vars['node'] via:
    <?php
    $vars
    ['my_new_var'] = render($vars['node']->the_piece_i_want);
    ?>
  3. in template_process_node(), render the rest of the content via:
    <?php
    $vars
    ['content'] = render($vars['node']);
    ?>
    [edit: oh! and in template_process_page(), $messages, $scripts, etc would need to be rendered after $content which neatly solves Moshe's issue, no?]
  4. in node.tpl.php, simply:
    <?php
    print $content;
    print
    $vars['my_new_var'];
    ?>

This also gets rid of the ugly print r(); issue emmajane mentioned too. So we could go back to a simple r(), but I actually prefer render():

<?php
$vars
['content'] = render($vars['content_array']);
?>
But I could live with r() since it would make Talk Like A Pirate Day more fun.

win, win, win?

#54

Damien Tournoud - May 28, 2009 - 17:09
Status:needs work» needs review

I wonder if implementing some kind of lazy rendering would not solve the issue. If the variable is an object, we can implement the __toString() magic method to do "on demand" rendering, directly in the theme template.

Of course it doesn't work when the template / theme function expects the child variable to be fully rendered for some reason (two examples in core: the page template expects everything else to be rendered, and the simpletest_test_table theme function expects the child checkboxes to be fully rendered too).

Here is a dirty proof of concept. Really dirty, I only wanted to see if it could actually work.

AttachmentSizeStatusTest resultOperations
455844-radical-new-idea.patch3.28 KBIdleFailed: 11480 passes, 53 fails, 5 exceptionsView details

#55

moshe weitzman - May 28, 2009 - 17:11
Status:needs review» needs work

It took me a few readings to grok JohnAlbin's suggestion. I really like it. I'll wait for some more feedback, and then I will reroll the patch as proposed.

#56

Benjamin Melançon - May 28, 2009 - 17:44

You mean once $vars['my_new_var'] is defined in template_process_node(), in node.tpl.php you can simply use:

<?php
print $my_new_var;
?>

Right?

And yes, I agree that we should either do all use of rendering functions in preprocess functions or provide a function that includes the print (say, print_render).

I favor JohnAlbin's solution of a best practice of print $var (rather than print function()) in template files and define and render parts of the variables in pre-process.

#57

sun - May 28, 2009 - 18:19

Hm. The more I think and read about this issue, the more I'm inclined to say that we need to support all possible rendering ways. That is:

  1. Render an element in $content. Don't output it when $content is rendered.
  2. Render an element in $content. And output it again when $content is rendered.
  3. Render and output elements in $content. And allow to output an element again afterwards.

If we don't do and provide all three variants, we are introducing a major WTF for Drupal themers. They have to deal with and study the internals of drupal_render() to find out how they can output the content in the way they want to. Not looking up any documentation will (of course) make them use the unsafe values of an element in $content again.

The current implementation seems to cover only 1) (the default).

A previous patch (#22) seemed to cover only 1) and 3).

But whenever you render an element individually, you have no chance to output it again within the regular $content.

FWIW, on recent follow-ups: While technically more clean, I wouldn't want to be forced to create yet another THEME_process_THING() function, (in general) detached from the template that is using it, just to shuffle some content elements around in the template/output.

Given Damien's idea, the above might translate into 1) $content->extract(), 2) $content->render(), 3) $content->output().

EDIT: Clarified 3)

#58

yched - May 28, 2009 - 23:10

- JohnAlbin's suggestion in #53 loops back to option 1) in my OP, except that the brand new template_process_node() step makes it actually viable (back then, it prevented CSS-only themes).

That's a way to do it. As sun points out, it means the logic moves from the template to (pre)-processors. What you want to do in there is in many cases specific to a node type (dependiing on node types fields or pseudo-fields), which is more easily done in templates (node-TYPE.tpl.php variants) than in processors (giant switch($node->type)).
On the plus side, it merges the points 2) and 3) in sun's #57 : if partial renders are prepared in processors, it doesn't matter in which order the template outputs them.

I think I'd personally favor the ability to work directly in templates (patch in #36), but I guess I could go with either approach.

- sun's 1) 2) 3) in #57 sound like optional flags for (drupal_)render()
1) is the default:

print render($content['foo']); // prints field 'foo'
print render($content); // prints the rest, doesn't reprint 'foo'

2) is a RENDER_ALLOW_REPRINT flag ("do not mark #printed = TRUE"):
print render($content['foo'], RENDER_ALLOW_REPRINT); // prints field 'foo'
print render($content); // prints everything, including 'foo'

3) is a RENDER_FORCE_REPRINT flag ("do not check for #printed"):
print render($content); // prints field 'foo'
print render($content['foo'], RENDER_FORCE_REPRINT); // prints everything, including 'foo'

As I said above, this last case is not needed of we go with JohnAlbin's processors.

Those flags are a) features for drupal_render b) can be added in a separate patch c) will most likely raise controversial discussions and whatnot.
Providing only 1) without 2) and 3) is already worlds ahead of what we currently have, and I strongly disagree about it being a "major drupal WTF". *Please* let's not scope creep this to death.

- I'm not sure I see how partial render of $content['foo'] would work with Damien's approach in #54

#59

eaton - May 29, 2009 - 03:11

I'm really warming up to JohnAlbin's proposal of using the new process phase. I'm a bit concerned about the phased complexity of preprocess, then process, then output via tpl.php, but I'm not sure that we can avoid it.

Also, my original idea for a dr() or render() or r() or p() function (whatever it would be named) was to have it support structured data OR strings. ie., its implementation would be something like:

function p(&$variable) {
  if (is_array($variable)) {
    print drupal_render($variable);
  }
  else if (is_string($variable)) {
    print $variable;
  }
}

The downside is that the person working with the tpl.php file still may need to understand that $content['field_blah'] is something meaningful, rather than $content. But training people to use p() or dr() or what not, regardless of the KIND of variable they're spitting out, would let them drill deep into the variable, or spit out simple strings, with the same function.

If people do their work in a process hook, that would leave us with simple 'just print out a string' tpl.php files again, and people splitting out chunks of $content would have to write PHP functions, right? And preprocess functions would be about 'assembling raw data for theming' rather than prepping strings?

#60

yched - May 30, 2009 - 02:31

OK - for clarity, let's name :

option 1 : JohnAlbin's solution - partial rendering of $content['foo'] is prepared in theme_preprocess, 'the rest of $content' is rendered in template_process [edit : fixed typo about process / preprocess steps]
Templates do regular 'print $prepared_var', and dont have to understand that some variables are strings, some others are render arrays.
Granular themeing has to be prepared in preprocessors, thus forcing you to maintain per node-type templates and preprocessors in parallel.

option 2 : we introduce "some" themer-friendly version of drupal_render() : render(), p()..., so that granular theming can happen directly in templates (patch #36).
Templaters have to be aware that some variables have to be treated special (render() - unless we can come out with a generic p() function, that can eat strings and arrays - see below).
You don't *have to* maintain preprocessors in parallel - but you can go that way if you want, option 2 "includes" option 1.

(+ option 3 : Damien's approach in #54 - still unclear to me)

About eaton's generic p() function in #59 : the discussion above raised two issues
- p('litteral string') breaks (because of by-ref param). Sad.
- it prints out of the box, so if you want to print $content['foo'] *after* 'the rest of $content', you need the flags mentioned in #58.

#61

emmajane - May 30, 2009 - 01:53

Some additional notes that will prove I either (1) understand or (2) need more help understanding the issues.

1. I like the idea of doing the work in the preprocess function. Conceptually this function is "really hard" for new themers, so I'm fine if it gets more complicated with things like case/if/else statements.

2. Now that I know it is possible to do it this way, I do NOT like the idea of having anything except
print $variable
in tpl.php files.
JohnAlbin is right: it will be hard to explain why some variables need it, but others don't.
eaton is also right: I would like it if the rendered $variable contained merely a string that could be printed in a template file.

3. I am neutral and leaning to "not-liking" the optional parameter to retain the extracted field in $content. If I'm extracting something but still want all content, I can create a custom $variable which contains the additional/repeated/redundant field (as I do now). I really liked having the option initially, but now I just think it's too much choice that will end up being more confusing than helpful.

I think those are all of my opinions. :)

#62

yched - May 30, 2009 - 02:15

emmajane, nick lewis on IRC, and obvioulsy JohnAlbin prefer the preprocess approach, so I suggest we call it a wrap.

re emmajane #61-3) : the current ways to reprint an already printed piece of $content (print $content['foo']['#voodoo_key'];) are not really ideal, so I'd still agree with sun about the need for a RENDER_ALLOW_REPRINT flag for drupal_render() - in a separate patch :-)

#63

Jeff Burnz - May 30, 2009 - 02:23

Subscribing

#64

moshe weitzman - May 30, 2009 - 03:57
Assigned to:Anonymous» moshe weitzman
Status:needs work» needs review

Here is the Albin method. Node system only. You can test the patch by applying it and clearing theme registry. Comments? I will proceed with profiles and pages soon.

AttachmentSizeStatusTest resultOperations
granular.patch3.07 KBIdleFailed: Failed to install HEAD.View details

#65

yched - May 30, 2009 - 12:44

To give an example of what #64 means for themers:
if you want to separate $content['field_foo'] and $content['field_bar'] for story nodes, you'd do

// in tempate.php:
function MYTHEME_preprocess_node(&$vars) {
  $node = $vars['node'];
  if ($node->type == 'story') {
    $vars['my_foo'] = drupal_render($node->content['field_foo']);
    $vars['my_bar'] = drupal_render($node->content['field_bar']);
   // the template engine will put drupal_render($node->content) ("the rest") in $vars['content']
  }
}

// in node-story.tpl.php:
...
<div class="floated-left"><?php print $my_foo; ?></div> <!-- Prints 'field_foo'. -->
<div><?php print $content ?></div> <!-- Prints the content, without 'field_foo', 'field_bar'. -->
<div class="floated-right"><?php print $my_bar; ?></div> <!-- Prints 'field_bar'. -->
...

Small gotchas:

- patch #64 means that $vars['content'] is the $node->content array in THEME_preprocess() (because of the "// Flatten the node object's member fields." line in template_preprocess), but is the rendered content string after template_preprocess(). Could lead to hair-pulling wtfs, we might want to take $node->content out of the flattened vars.

- the most natural thing you'll do is probably name your 'partial render' variable "$field_foo", thus smashing the existing $field_foo which contains the raw $node->fied_foo data. Themers "shouldn't" need those raw data anyway, but that could still lead to annoying debug sessions...

Also, going for the 'preprocess' approach means that this new, recommended way of doing granular rendering has a chance to remain unnoticed by themers with a pre-D7 background, which will continue doing the ugly, unsafe manual themeing from raw data they were used to. I'm not sure people read the "D6 -> D7 theme upgrade" page if they don't upgrade an existing theme but start a new one.
The other option ("print $content" becomes "print render($content)") was more disruptive, and thus couldn't go unnoticed.

#66

Frando - May 30, 2009 - 17:02

Actually, I liked the first approach taken much more (using render() or r() or whatever directly in templates). With the approach in the latest patch, a themer that wants to put one cck field in a different place still has to create or edit template.php, write an actual php function with a special name, modify the $variables array by reference... not really any easier than currently.

With render() inside templates, though, the average themer just has to learn to use one php function, and can then just do what he wants to do, without ever touching template.php. So to print out a cck field at a specific location, it's just print render($content['field_foo']); and then print render($content); for the rest. In my opinion this is really straightforward. Also, we can then for example add two more utility functions, show() and hide() that simply modify the #printed attribute of a renderable array element. So you could do the following in a template:

<?php
// This prints out field_foo
render($content['field_foo']);

// This prints out everything in content except field_foo and field_barr
hide($content['field_bar']);
render($content);

// This prints out field_bar
show($content['field_bar']);
render($content['field_bar']);
?>

The three functions here would be very simple, nothing more than:

<?php
function render(&$element) {
  print
drupal_render(&$element);
}

function
hide(&$element) {
 
$element['#printed'] = TRUE;
}

function
show(&$element) {
 
$element['#printed'] = FALSE;
}
?>

In my opinion, this is much easier than having to implement a preprocess function whenever you want to render the node content (or everything else that uses renderable arrays) slightly different. Remember that we are using renderable arrays more and more. I really think the average themer can easily learn to deal with these three functions and renderable arrays. This saves us lots of preprocess functions and doesn't force themers to write php functions in many places.

#67

Frando - May 30, 2009 - 18:24

About the concerns raised in #51 regarding #attached_css making it impossible to completely late-render the parts of the $page array in page.tpl.php: We just contemplated in IRC about a seperate iteration over the $page array prior to passing it into the theme layer (but after hook_page_alter) that would handle #attached_css and #attached_js and could also handle more stuff in the future like caching (http://cyrve.com/node/7) or a #render_callback that allows late-loading of data.

Then, even page.tpl.php could just receive a $page variable and the regions and other elements are printed via render($page['left']), render($page['content']), render($page['breadcrumb']) etc (as I proposed in #66).

#68

webchick - May 30, 2009 - 20:50
Issue tags:-RDFa+RDF

Fixing tag. Other RDF-related core issues seem to be under "RDF" rather than "RDFa"

#69

moshe weitzman - May 31, 2009 - 04:10

I'm swinging back and forth like a spineless cheerleader. I could easily do the Albin method or the yched method (use render() in the tpl.php itself).

I really want some solution for #67. I tried to add a drupal_prepare_page() after hook_page_alter() that handles #attached_js and #attached_css. But my recursion isn't looking clean or even working properly. We need some solution for this before I can get fully behind the render() proposal. help wanted.

#70

Dries - May 31, 2009 - 11:19
Issue tags:+Favorite-of-Dries

I've just spent 30 minutes thinking about this, and I'm also swinging back and forth. It might mean we haven't cracked the code yet.

I think I'm leaning towards render(). The process function are rather complex.

Maybe it would be nice to be able to do: render($content['bar'], 'process_bar') where 'project_bar' would be an explicit process function. ;-)

#71

Jeff Burnz - May 31, 2009 - 11:46

Will using the render() method change how we work with variables in pre/process functions?

#72

eaton - May 31, 2009 - 15:55

I've just spent 30 minutes thinking about this, and I'm also swinging back and forth. It might mean we haven't cracked the code yet.

Indeed. Long-term, I think it's one of the reasons that making renderable things objects, and overriding their toString() method to facilitate rendering, is an excellent approach. We could use print, and hide any behaviors we want to under the hood.

In the near term, though, it feels like we are choosing between the straightforward cleanliness of the tpl.php's Earl championed in D6, and the flexibility of render(). While the cleanliness is important, if we had to choose I would probably lean towards a render() approach that can take strings or arrays; yched's note about string literals doesn't bother me much, render('foo') doesn't concern me too much. are there many folks out there using print for literals in tpl.php's, rather than just leaving the literals in the tpl.php's un-parsed markup?

Will using the render() method change how we work with variables in pre/process functions?

Well, there would definitely be a stronger emphasis on leaving some things 'unrendered' before they hit the tpl.php files, rather than pre-rendering them into strings. Right now preprocess is used to turn data into rendered strings. That's an important philosophical shift.

#73

Frando - May 31, 2009 - 18:17

So I tried out what problems it would bring if even the page regions would be rendered as late as possible, that is, in the template file.

The basic problem is, as outlined in #51, that we are then printing $styles and $scripts in page.tpl.php before the main page content is rendered.

In the attached very WIP patch I splitted drupal_render() in two parts: drupal_process_elements() which processes the elements (merging in default values, calling #pre_render, handling #attached_css and #attached_js) and drupal_render(), which really only renders the element and calls #post_render.
The two functions can be called independently. drupal_render will call drupal_process_element on elements that haven't been processed, though. This required moving block contents out of the #block->content property into an actual renderable element so that the block contents get caught by drupal_process_elements().

This means that #attached_css and #attached_js are handled fine now. What remains, though, is that it is not possible to call drupal_add_css and drupal_add_js in theme functions that are called from drupal_render via an element's #theme property, as that would happen too late (at least for menu callbacks that return unrendered arrays, which is the recommended way).

So - if we want to move towards completely rendering the page as late as possible, we have to move drupal_add_css / drupal_add_js out of theme functions and into #attached_css and #attached_js properties.

Also in this patch I converted node.tpl.php towards render(), show() and hide() to give an example on what I outlined in #66.

Note that these two parts of the patch are independent. We could move towards render(), show(), hide() without problems by leaving page.tpl.php alone (that means, rendering the regions in preprocess_page instead of page.tpl.php) if we think that rendering the main content in the template involves too much struggling. I would be completely fine with this.

AttachmentSizeStatusTest resultOperations
render_late.patch26.03 KBIdleFailed: Failed to apply patch.View details

#74

Frando - May 31, 2009 - 18:34

Attached is a patch that uses render(), show() and hide() as explained in #66 without trying to convert page.tpl.php (and therefore not having to deal with css and js addition issues). See node.tpl.php on how one could use show() and hide() and also how we can then remove code from the preprocess function without making things harder for the themer.

Edit: Now that is cool, 100% passes for #73! Didn't expect that.

AttachmentSizeStatusTest resultOperations
rendertheming.patch8.26 KBIdleFailed: 11571 passes, 0 fails, 1 exceptionView details

#75

System Message - May 31, 2009 - 18:30
Status:needs review» needs work

The last submitted patch failed testing.

#76

Frando - May 31, 2009 - 18:35

Also: I agree with eaton and others that in the long term, moving our renderable structures towards objects is the way to go. This makes many things easier, the syntax can basically stay the same thanks to ArrayObjects, but for example we could get rid of #properties (by using -> syntax for them), we wouldn't have problems with losing references and we could use __toString for final rendering.

Reminds me of the "FAPI4" brainstorming code I did some two years ago (the code is in my sandbox, I've extracted some doxygen documentation. See especially the documentation of the DrupalElement class. The biggest problem with this code was that it heavily relied on circular references. These are quite a pain in PHP (regarding memory consumption), which I didn't know back then. And, of course, that it tried to rewrite half of Drupal, which usually is quite a problem by itself ;)
I still think though that there are some quite interesting and useful concepts in that code.

#77

yched - June 3, 2009 - 22:13

As I said a couple times before, I'm also in favor of 'render() in templates' vs 'preprocessor only'. It's far more flexible, easy to customize node-type by node-type (which is the most common use case), and you can still pre-render your variables in preprocessors if that's how you want to work with your templaters.

The one thing I don't want is see D7 punt because we couldn't pick between two solutions that are both immensely better than the status-quo :-). We need something in.

I was a bit skeptical with Frando's hide() and show() methods at first. Flags for drupal_render (as I proposed in #58) seem more inline with the usual drupal way. Then I figured that templates are a different world addressing a different audience, for which hide() and show() are probably easier to grasp and remember. So +1 from me.

I'm also not sure page.tpl.php *needs* to be converted to late in-template render() if that means important refactoring of the render process. Enforcing 100% consistency is not really important IMO, just use the best tool for the job. Are there actual use cases for late rendering of page regions ? If not, leaving them as flat, pre-rendered variables in the page template is not an issue.

I frankly dislike, however, the removal of *all* preprocessed node content (terms, links, comments) in #74. The consequence in node templates is awful :

-    <?php print $content ?>
+    <?php hide($content['comments']); hide($content['links']); render($content); ?>

I'd vote to keep comments, links and terms pre-rendered in the preprocessor, as was done in the earlier 'template based' patches.

#78

moshe weitzman - June 4, 2009 - 13:21

@Frando - I think drupal_process_elements() is too much struggling. Let's use the Albin pattern for page while other templates use the yched's render() within the tpl.php technique.

I like how the patch leaves rendering of terms/links/comments to the template. Many sites disable these and there is no point in generating HTML that will never be used. These are nice examples for template authors about how to use the new functions. I agree it looks a little odd at first, but in the end I think it is empowering for themers to see this in action.

I'm certain we need hide()/show() in addition to render().

If noone gets to this sooner, I can probably roll a patch on Saturday.

#79

moshe weitzman - June 11, 2009 - 04:33
Status:needs work» needs review

OK. Here is a happy hybrid approach. As mentioned, page uses Albin approach since we can't use yched approach there. node and profile use yched apprach, and the templates principally use a new render() function which is an mostly alias for drupal_render().

I also brought over hide() and show() from Frando's patch. Those are used in the node template, as he proposed in #74. They might look a bit odd at first (#75) but they are very empowering for the themer and I like having usage examples in core.

I am getting tons of exceptions and a login failure running tests on my machine. Lets see if testbot can do better. I am also not seeing taxonomy and comment node links for some reason. Hopefully the tests will flush it out.

AttachmentSizeStatusTest resultOperations
granular.patch17.02 KBIdleFailed: Failed to install HEAD.View details

#80

catch - June 11, 2009 - 08:44

Some debug left in.

+  // var_dump($variables);

Not marking to CNW since there's plenty still to review.

#81

moshe weitzman - June 11, 2009 - 11:47

Without that debug line.

AttachmentSizeStatusTest resultOperations
granular.patch17.02 KBIdleFailed: Failed to apply patch.View details

#82

Frando - June 11, 2009 - 12:51

I like the patch a lot.

Attached patch only removes some more debug code that was left and improves documentation a tiny bit, no functional changes.

IMO this is RTBC. This hybrid approach seems to be the best and easiest for the moment. It is a lot better to what we have now. Using render(), show() and hide() in templates gives a lot of flexibility to themers, and it's a lot easier to learn than learning to use process functions and stuff. So if JohnAlbin or some other themer is OK with the patch, go ahead and RTBC it.

AttachmentSizeStatusTest resultOperations
granular.patch14.29 KBIdleFailed: Failed to apply patch.View details

#83

yched - June 11, 2009 - 13:21

Fine for me too, but I obviously can't RTBC myself.

#84

mfer - June 12, 2009 - 16:01

/me drools and subscribes

#85

tic2000 - June 12, 2009 - 23:08

+function show(&$element) {
+  $element['#printed'] = FALSE;
+  return $element;
+}
+
+
+/**

One too many new lines here?
Everything else looks fine.

#86

Frando - June 13, 2009 - 10:54

Removed the extra newline.

AttachmentSizeStatusTest resultOperations
granular.patch14.29 KBIdleFailed: Failed to apply patch.View details

#87

System Message - June 14, 2009 - 12:30
Status:needs review» needs work

The last submitted patch failed testing.

#88

noahb - June 15, 2009 - 21:28
Status:needs work» needs review

re-roll of the patch from #86

AttachmentSizeStatusTest resultOperations
granular_theme-re-roll.patch15.32 KBIdleFailed: Failed to apply patch.View details

#89

moshe weitzman - June 15, 2009 - 21:57
Status:needs review» reviewed & tested by the community

frando and yched and myself are all in agreement that this is RTBC. I'll be the one to mark it so.

#90

eaton - June 15, 2009 - 22:32

I've been looking this over and ran it past a number of the folks who showed up at the Drupal For Designers event. General consensus (at least among those I talked to) is that it's really, really awesome. Moving towards these kinds of structures allows us to treat something like $content as a standalone entity, OR dip in for a tiny piece and render the rest later, AND preserve the safety of drupal_render's output scrubbing. While preprocess functions allow this too, the leap from 'manipulating a template' to 'writing a preprocess function' requires juggling a lot of extra conceptual clutter. I'm cool with it as a developer, but in conversations with them, I think I've come to the conclusion that this feature smooths the "learning ledge" a lot.

I'd like to bikeshed about the use of render() as the function name -- p() or r() might be nice shortcuts, but that's beside the point.

#91

Dries - June 17, 2009 - 20:11

Patch looks good to me, so let's go for it!

#92

webchick - June 18, 2009 - 04:45
Status:reviewed & tested by the community» needs work

Yay! Awesome. A couple of questions.

+  if (!empty($variables['page']['left'])) {

Should we put this into something like $variables['page']['regions']['left'] ?

+ * in $variables['page] during THEME_preprocess_page().

['page']

-    <?php print $content ?>
+    <?php hide($content['comments']); hide($content['links']); render($content); ?>

Yuck. :( Why do we have to do this again? It's not clear from the code, and I tried reading through the issue to spot it and only saw the reason that it's nice to have usage examples in core.... well. maybe. but not if it makes the code 10x harder to understand. At the very least we should comment this in the default template.

Looking at the new template output, we do introduce inconsistency (sometimes we use print $thing, sometimes we use print render($thing)), which may become problematic. I guess we'll find out as more themers start working with Drupal 7. But one nice benefit of this inconsistency is it's clear when you are dealing with a 'complex' data structure vs. just a regular ol' string. Once themers learn what render() can do for them, they can view any template and go nuts with switching things around.

So please answer my two inquiries, fix the comment typo, and resolve the last item either through documentation or by removing it, and then this is good to go, unless someone chimes in and says otherwise.

#93

moshe weitzman - June 18, 2009 - 15:06
Status:needs work» reviewed & tested by the community

1. I don't think so. The only renderable entities in $page are regions so I see no need to nest them inside a regions element. Anyway, that's not strictly part of this issue. The $page array structure should be decided before this code runs.

2. Fixed

3. The goal of this issue is to let themers render parts of the node/profile and keep their presentation working when they install new presentation elements such as fivestar. This goal can be accomplished be doing rendering only in preprocess. Thats the 'Albin' method. The yched/frando method is a superset of the Albin method. Themers can alternatively choose to do rendering in a tpl.php file. Thats what this patch does for node and profile templates. I added a mention of hide() in the $content section of node.tpl.php. We already discuss render() there. Further discussion of how to write a template belongs in the theme guide, IMO.

AttachmentSizeStatusTest resultOperations
granular.patch14.96 KBIdleFailed: Failed to apply patch.View details

#94

mortendk - June 18, 2009 - 15:13
Status:reviewed & tested by the community» needs work

This looks really really really cool!!

Is the idea then that the themer could go in and do somethink like: hide($content['links']['read_more']); to remove that or am i going down the wrong path?

small pieces of that kinda logic would be pretty awesome to have. I know its logic and should not go into the theming layer and yadi yadi yadi ;) ... but please ... pretty please ...

imho it will not be a problem for a themer to understand render($foo); its just telling 'em "hey this is the way drupal does it baby!" just as long its gonna be the same for every in the tpls (i had a huge problem understanding that $node was the array i had to grap to split up the $content

The problem themers still have (again imho) is: So why is drupal putting: FOO around my foo i only asked for the render($content['foo'])
hmm maybe a render($content[foo][#clean]) to get out and around the druaplistic markup? - and make both sides of the theming way happy ?

#95

moshe weitzman - June 18, 2009 - 15:44
Status:needs work» reviewed & tested by the community

@mortendk and i cross-posted. resetting status.

yes, hide() how you would suppress the read more link. you could alternatively use hook_page_alter() but thats perhaps out of reach of template focused themers.

this issue is not strictly about markup. we are just discussing how to give more control about what prints where while keeping the "print everything else' feature.

#96

Rob Loach - June 18, 2009 - 18:03

The thing I don't like about this patch is that we're adding more PHP to the template file. In node.tpl.php, you see:

<?php
// Before patch
 
<div class="content">
    <?
php print $content ? >
  </
div>

  <?
php print $links; ? >

  <?
php print $comments; ? >


// After patch
 
<div class="content">
    <?
php hide($content['comments']); hide($content['links']); render($content); ? >
  </
div>

  <?
php print render($content['links']); ? >

  <?
php print render($content['comments']); ? >
?>

Don't we want the tpl.php files to be just minimal HTML with only outputting PHP variables? The Drupal NYC June meetup was yesterday and we talked about what went on at D4D and I was really impressed with the Studio theme did it. It forces you to just register the variables in the preprocess functions, and then gives a granular folder hierarchy for the overrides.

My thoughts are have the render() and hide() calls in preprocess functions, and then have the .tpl.php files just look something like this:

<?php
 
<div class="content">
    <?=
$content ? >
  </
div>

  <?=
$links ? >

  <?=
$comments ? >
?>

#97

moshe weitzman - June 18, 2009 - 18:01

There is nothing *forced* about this approach. You can do your templates with just print statements if you wish. if you read the issue, we discussed the tradeoffs at length and decided to give the template author more power in the template *if he wants it*.

I love studio's approach too but it isn't really related to this issue. This is about more granular rendering, not injecting attributes into the template.

#98

yched - June 18, 2009 - 19:03

Removed an unrelated hunk in node.install.

re Rob Loach #96: [edit: crossposted with moshe]
"The thing I don't like about this patch is that we're adding more PHP to the template file / Don't we want the tpl.php files to be just minimal HTML with only outputting PHP variables"
We've been through this. This is #60 'option 1' vs 'option 2'. Since then, it seems a clear majority raised in favor of option 2.
Sticking to the (IMO short-sighted) statement that 'templates should just output php variables' means any flexibility needs to go in preprocessors, which requires *much* more drupal and PHP knowledge from themers.
The PHP added in the templates has been deemed simple and straightforward enough, even by theme-oriented people, as shown in several followups above.
*And* this approach still lets you work in preprocessors and keep straight prepared variables in templates if that's how you prefer to work.
So I see no reason to loop on this question.

re webchick #92:

-    <?php print $content ?>
+    <?php hide($content['comments']); hide($content['links']); render($content); ?>

I initially thought 'yuck' too, but I now think that templaters will more easily grasp how they can do granular templating if they find an actual working example already in place.

AttachmentSizeStatusTest resultOperations
granular_templates-455844-97.patch14.51 KBIdleFailed: Failed to apply patch.View details

#99

JohnAlbin - June 18, 2009 - 20:06
Status:reviewed & tested by the community» needs review

I really like the preprocess/process changes I see for page.tpl. :-)

The old template_preprocess_user_profile() used to uasort by weight. It looks like this was duplicate work since element_children() already sorts by weight. So nice optimization!

I updated the phpdoc blocks for render, show, and hide. Since it wasn't obvious to me why render()ing an element after hide()ing it worked, i.e. without first show()ing it.

I added a comment to the node.tpl.php to briefly explain why we are hide()ing stuff.

Also it looks like in modules/node/node.tpl.php, we were doing a print render($content['links']); which since render already prints seems redundant. I've removed the print bits; it seems to still work. Note: you need to test the default node.tpl.php using the Stark theme, since Garland uses a different node.tpl.

And, I removed the // print '<pre>' . check_plain(print_r(get_defined_vars(), TRUE)) . '</pre>'; comments from the tpls. The idea is very good, but the implementation is too scary for a beginner themer. If you uncomment them, it looks like a debugger threw up on the page. :-)

The 2 slight fixes (node_update_7004() mod removal and print removal from node.tpl) need review. Otherwise, this patch looks RTBC to me. :-)

AttachmentSizeStatusTest resultOperations
granular-455844-99.patch15.25 KBIdleFailed: Failed to apply patch.View details

#100

moshe weitzman - June 18, 2009 - 20:56
Status:needs review» reviewed & tested by the community

Nice tweaks, JohnAlbin.

#101

webchick - June 18, 2009 - 21:19
Status:reviewed & tested by the community» needs work
Issue tags:+Needs Documentation

Excellent.

Render experts approve
#drupal-design approves
Testing bot approves
And both core committers approve!

I found one last thing when I was reading through:

+ * it will contain all it's profile items. By default, $user_profile['summary']

it's should be 'its'

So fixed that, and committed to HEAD!! :) Woohoo!!! Marking needs work until it's documented in the module(?) and theme upgrade guides.

#102

yched - June 18, 2009 - 23:31

So. Cool. Many thanks to the many people that helped.

We can now remove one ugly bit of the Field API : #367215: Remove 'exclude from $content' display setting

#103

yched - June 19, 2009 - 00:03
Status:needs work» reviewed & tested by the community

Quick fix : print render() -> render() in user-profile.tpl.php

AttachmentSizeStatusTest resultOperations
granular_templates-followup-455844-103.patch550 bytesIdleFailed: Failed to apply patch.View details

#104

sun - June 19, 2009 - 00:24

I somehow assumed that the omitted "print" before render() was left out by mistake in previous follow-ups. Why does render() print instead of return now? I've read back, but could not find the reason for this change.

For reasons to have render() return (not print), see #43 and earlier.

#105

webchick - June 19, 2009 - 00:37
Issue tags:+Needs design review

I committed #103 so that this is consistent with the other templates. I thought this was a failing in our test coverage, but for whatever reason the user profile in stark seems to show up fine even without the patch. Curious.

If we go sun's way (return rather than print), it adds more PHP to templates which themers seemed not to like (print render($foo) as opposed to render($foo)). You could also argue that if you're going to be sticking things into variables and calling other theme functions, you're into "that ought to go in template.php" mode, where render() is not necessarily intended to be used. But we can discuss it. Could some of the #d4d people chime in with their thoughts?

#106

webchick - June 19, 2009 - 00:39
Status:reviewed & tested by the community» needs work

back to needs work for docs.

#107

geerlingguy - June 20, 2009 - 05:31

Less PHP in the template files = more design pleasure, for me at least.

To my OCD-ified self, seeing all this PHP stuck in the middle of my wonderfully-laid-out and formatted markup makes my heart skip a beat or two. I can live with a little (it's what makes a dynamic website possible), but the more code you put in, the less 'template-y' it feels, and the more WordPress designers feel justified in saying "Drupal is built for programmers." :-/

However, if the code is well-commented, and if themers and beginning Drupal designers can find out what all this stuff means easily and quickly, then I'm all for it.

#108

webchick - June 20, 2009 - 06:31

That appears to be an argument for sticking with render() as opposed to print render() (slightly less PHP).

As discussed in #drupal, although this definitely does stick more PHP in themers' faces, once they learn the hide()/render() pattern, there is far, far less monkeying about with theme/variable overrides in template.php, or trying to find every single element of the $content array because you're forced to print them all out manually if you want to customize it in any way. These alternatives are all as "PHP in your face" as you can get.

We tried to reach a compromise of not running *all* variables through render(); for example, page.tpl.php remains mostly print $var statements. But for something like node.tpl.php, where teasing apart individual pieces and sticking them at random places is a pretty common occurrence, render()/hide() pass along a ton of power.

#109

yched - June 20, 2009 - 18:14

re geerlingguy: please re-read #98.

re sun; webchick: It's true that the current use of render() is kind of "in between".
IMO we have two directions:

1. Since render() currently takes care of print and accepts both flat $var strings and $content arrays, we could:
- rename it p()
- make it the generic, consistent way to print stuff in templates: print $title; -> p($title); everywhere.
We don't need to explain '$content is a bit special, you have to use render()", but instead "oh, with $content you can *additionally* do cool things using show() and hide()", which is a little less imperative.
Drawback: p('litteral') or p($count % 2 ? 'odd' : 'even') cause fatal error, which is a bit tough - node templates used to have some for some CSS classes until recently. Field templates currently have a few.

examples:

<div><?php p($title); ?></div>
<div><?php hide($content['foo']): p($content); ?></div>
<div><?php print $count % 2 ? 'odd' : 'even'; ?></div>
or
<div><?php $zebra = $count % 2 ? 'odd' : 'even'; p($zebra); ?></div>

2. If the inconsistency with literals is problematic, we can decide we don't need a generic print function, and get the call to print out of render().

<div><?php print $title; ?></div>
<div><?php hide($content['foo']): print render($content); ?></div>
<div><?php print $count % 2 ? 'odd' : 'even'; ?></div>

#110

eaton - June 20, 2009 - 20:19

Personally, I'm in favor of p()/render()/whatever() our generic utility printing function. It handles renderable arrays, it handles strings, and if we wanted to be crafty, we could even check is_object() and handle that differently as we migrate portions of Druapl to OOP code. p() or render() could stay a consistent interface for 'outputting stuff'.

#111

sun - June 20, 2009 - 20:41

That is not really my point. Whether it's called p(), render(), or whatever(), in all cases, you can do:

<?php
p
();
render();
whatever();

||

print
p();
print
render();
print
whatever();
?>

Just returning and not printing in render() might also avoid confusion:

<?php
show
(); // Does that output something?
render(); // Why does this output something?

print "foo"; // Always outputs something.
print render(); // Clearly outputs the rendered content.

render(); // Just renders.
print l(render($content['field_url_title']), 'node/' . $node->nid);  // Renders, links, and outputs.
?>

#112

yched - June 20, 2009 - 21:26

sun: which functions print and which don't is exactly the point of comment #109:
option 1 removes the ambiguity by stating that all prints are done by a p() function (currently named render()). p is for 'print', so it should be clear that this function prints and the others don't.
option 2 removes the ambiguity by stating that no function prints, all printing needs an explicit print. No p() function, render() stays render() and doesn't print.

Your last example, though (use the output of a render to build something else), is more problematic with option 1. You raised this use case several times in this thread, I have to confess it slipped my mind during the latest evolutions of the patch, sorry.

Updated options to account for this:

Our current code is:

<div><?php print $title; ?></div>
<div><?php hide($content['field_url_title']): render($content); ?></div>
<div><?php render($content['field_url_title']); ?></div>
<div>Not doable: <?php print l(render($content['field_url_title']), 'node/' . $node->nid)); ?></div>
<div><?php print $count % 2 ? 'odd' : 'even'; ?></div>

1.
- remove the 'print' from render().
- introduce function p(&$var) {print (render($var));
- make p() the generic, consistent way to print stuff in templates: print $title; -> p($title); everywhere.
We don't need to explain '$content is a bit special, you have to use render()", but instead "oh, with $content you can *additionally* do cool things using show() and hide()", which is a little less imperative.

<div><?php p($title); ?></div>
<div><?php hide($content['field_url_title']): p($content); ?></div>
<div><?php p($content['field_url_title']); ?></div>
<div><?php p(l(render($content['field_url_title']), 'node/' . $node->nid)); ?></div>
<div><?php print $count % 2 ? 'odd' : 'even'; ?></div>
or
<div><?php $zebra = $count % 2 ? 'odd' : 'even'; p($zebra); ?></div>

2. Rename render() to p(), keep the 'print' in there, add an optional $return param (just like print_r() has)

<div><?php p($title); ?></div>
<div><?php hide($content['field_url_title']): p($content); ?></div>
<div><?php p($content['field_url_title']); ?></div>
<div><?php p(l(p($content['field_url_title'], TRUE), 'node/' . $node->nid)); ?></div>
<div><?php print $count % 2 ? 'odd' : 'even'; ?></div>
or
<div><?php $zebra = $count % 2 ? 'odd' : 'even'; p($zebra); ?></div>

3. We don't need a generic print function, and simply remove the 'print' out of render().

<div><?php print $title; ?></div>
<div><?php hide($content['field_url_title']): print render($content); ?></div>
<div><?php print render($content['field_url_title']); ?></div>
<div><?php print l(render($content['field_url_title']), 'node/' . $node->nid); ?></div>
<div><?php print $count % 2 ? 'odd' : 'even'; ?></div>

#113

geerlingguy - June 21, 2009 - 06:53

re: yched/#109 / #98:

I did read that comment (and re-read it)... one thing that I've noticed about this whole discussion is the use of the term 'themer,' and how themers have come to accept the idea of extra PHP in the name of helping them more easily theme things...

I come more from the perspective of 'designer,' and I believe there's a bit of a difference between a designer and a themer. A themer, in my opinion, would be one who is more of a mix of programmer and designer, whereas a designer is someone who designs, and doesn't do a ton of 'theming' in Drupal-speak. For a lot of Drupal shops and design firms, a programmer-themer is not hard to find. For a one-man operation, or for an individual coming from a Dreamweaver/WYSIWYG editor who attempts to design a Drupal site (but has little programming experience), not so much...

My point is not to say that the render() stuff here is a bad idea. I just want the main Drupal developers to remember that designers are not necessarily Drupal themers, and in all these kinds of decisions, make sure you explain things and do them in a way that is accessible to non-programmers.

Especially in these .tpl.php files, which many designers (like myself) will be touching, make sure you leave a comment in if there's something weird going on inside. Or at least put a link to a node explaining things on drupal.org.

#114

Benjamin Melançon - June 21, 2009 - 07:39

Plus one for yched's Option 1 in #112.

If we put print in any function, it should be a wrapper (of sorts, at least) for the original function.

Making p() our generic printing function, with render() still out there without a print, is ideal. People who want the .tpl.php files to be only 'print $var' style can put rendering in preprocess functions. So whatever we do, let's not embed 'print' in a larger render() function (even with a print_r style return parameter).

An unscientific poll of themers and designers at Agaric suggests that p() for intelligent printing could become a much-loved Drupalism.

benjamin, Agaric Design Collective

#115

moshe weitzman - June 23, 2009 - 03:34

Well, #2 is #1 plus the ability to fulfill's sun's l() example without resorting to output bufferring. I like #2.

I am working on docs for this issue.

#116

moshe weitzman - June 23, 2009 - 04:00

Docs posted to http://drupal.org/node/254940#granular. Feel free to improve them. I think we should add that p() function so leaving this at CNW.

#117

yched - June 23, 2009 - 12:51

Also, #1 (function p(&$var) {print (render($var));) adds a nested function call for every 'non delayed print' operation, which remains the most common case. For flat variables, this means two nested function calls around a simple print $title...
So I also think #2 is more reasonable.

#118

eaton - June 24, 2009 - 07:15

My vote is for #2 as well -- defaulting to print, with the option to return, gives a lot of flexibility with minimal overhead and avoids yet one more layer of call stack (p() to render()).

#119

yched - June 29, 2009 - 23:18
Status:needs work» needs review

OK, here's the beginnings of a patch for #112-option2 : p($var, $return = FALSE)

This outlines the occurrences that cannot be moved to p() - some denote code that could go in preprocessors, but not all.

print t('Updated:');
print $block->module . '-' . $block->delta;
print empty($block_listing[$region]) ? 'region-empty' : 'region-populated';
print $row % 2 == 0 ? 'odd' : 'even';
print $data->row_class ? ' ' . $data->row_class : '';
print implode(', ', $categories);
...

Moving print to p() in all templates is a bit tedious, so I did the first few (up to book.module) for now, and punt for confirmation that this is where we want to go.

AttachmentSizeStatusTest resultOperations
generic_p-455844-119.patch16.15 KBIdleFailed: 11167 passes, 572 fails, 86 exceptionsView details

#120

System Message - June 30, 2009 - 01:20
Status:needs review» needs work

The last submitted patch failed testing.

#121

yched - July 1, 2009 - 00:38
Status:needs work» needs review

Forgot to update render() to p() in garland's node.tpl.php. Failure flood ensues.
Other than that, see comment in #119.

AttachmentSizeStatusTest resultOperations
generic_p-455844-121.patch21.1 KBIdleFailed: Failed to apply patch.View details

#122

webchick - July 1, 2009 - 02:11

Hrm. I'm having a war right now between my profound desire for consistency everywhere, and the fact that this change would mean shoving Drupalisms on each and every designer/themer.

print $var;, though a tiny bit longer to type, at least is standard PHP, recognizable from anywhere, and portable anywhere. p() is a Drupalism, which is going to require a trip to the API documentation for every single person who tries to theme Drupal. (p()? What is that? Browse, search, read... Huh. Why not just print then?)

render() is of course yet another Drupalism, but it currently only affects certain, select templates (node, user, etc.). I think it's conceptually simple to say "If the thing you're trying to print out is complex, such as a node or a user profile, as opposed to a page title, then you use the render() function." Themers already know they can't print $var when $var is an array or object. Those things are "special." So would be be that much of a stretch to tell them to use render() on things they can't print? I don't really know.

I will ask some designers/themers to take a look at this patch once again. Thanks for the nice start, yched.

#123

eaton - July 1, 2009 - 02:29

webchick: personally I think that consistency is more valuable. Inconsistency and a myriad of special cases is the bane of a Drupal newcomer's existence. As developers we've spent years frightened of showing themers even a smidgen of PHP, and working to eliminate everything but print() from our tpl files. While that is sometimes helpful, a far bigger problem is our deeply, massively nested tree of templates and variables.

Folks talks about designers favoring wordpress; it has nothing *but* custom one-off functions for every kind of data, and templates don't contain calls to print() at all. The difference isn't the lack or presence of PHP, but the complexity of the mental model necessary to build a working theme. If using p() or render() allows us to (even in some cases) reduce the amount of deeply nested drilling that people have to do to exercise control over the output, it will be a net win IMO.

Feedback from real designers who do real theming is absolutely desired, but I think we need to remember that complexity is a matter of mental models. Catering to those who don't know what a function is, but understand that they need to drill down to content-field--image-0-picture.tpl.php to change the ID of a wrapper div, seems like the 'Designer Experience' version of misplaced optimization.

#124

Jacine - July 1, 2009 - 22:43

Seeing the options laid out in #112 really has me torn. I keep reading it over and over and was debating over whether or not to post but here goes...

I understand the reasons behind having this all in a template file, and that goal is to hide the complicated nature of $content, but that fact is that it IS complicated and this patch makes it VERY cool at the same time, so why hide it or dumb it down?

I know this is going to sound weird, but the more I think about p() all over the place, the more I think it's a little insulting in a way. If I were just learning, I'd feel like I was being tricked into thinking that I need to do <?php p($title); ?> when really there is no reason. I'd also feel like drupal developers thought I was too dumb to understand the reasons behind it. It's very obvious what 'print' is in <?php print $title; ?>. People are used to it, code editors know what it means and highlight it so it's easy to read, snippets all over the place have it, and if someone really cares they can look it up on php.net. Also, since print still exists in a bunch of places as noted in #119, it's not consistent, which is confusing and hard to explain.

The point is, I think explaining what's going on and not dumbing it down is better, so I'm opting for #3. I'm not sure if there are plans to change this where theming forms are concerned, but at least this method seems most consistent with theming forms. If there aren't plans for that, I'd rather see <?php print drupal_render($content['field']; ?> and <?php unset($content['field'); ?> for consistency. OMG, did I just say that out loud?

/me dodges bullets, hides and hopes I didn't miss anything obvious.

#125

eaton - July 1, 2009 - 23:07

Well, technically unset($content['field']) isn't what's going on under the hood of the functions in question. It's really setting $content['field']['#printed'] = TRUE, otherwise it would be impossible to 'show' it again in the future.

Also, it's important to keep in mind that p() is intended to maintain consistency across all the variables that are hended off to a tpl.php file -- whether they're strings, structured arrays, or what not. If that sounds like it isn't worth pursuing, so be it. But it will require that we educate people in a lot more depth about the difference between "a variable you have to render" and "A variable you can print."

Or, go back to the D6 approach and hard-render everything in a preprocess function.

#126

Jacine - July 1, 2009 - 23:43

I didn't realize about the #printed = TRUE part, though I'm quite used to doing <?php $vars['field'] = drupal_render($field_whatever); ?> with forms, so I can show it later so it seems familiar.

Personally, I'd prefer the education, but I will also be doing all of this type of stuff in preprocess functions anyway, so I'm probably not the targeted audience for feedback here. For me, that's the "right" way of doing it. If I was asked, that's how I would tell someone to do it (preprocess), but I wouldn't want to push that on people :)

I guess comments from newbie themers would be more helpful... Where are they?

#127

dvessel - July 2, 2009 - 00:21

Just subscribing for now. I need to look into this further.

#128

sethcohn - July 2, 2009 - 01:31

I actually find option 3 in #112 the most appealing: render shouldn't print... print should print, and render should only render. In cases where you want to generate but NOT print the rendered item, you should be able to trust that nothing was printed, and the entire item is capturable.

print is standard php, p() is going to end up a confusing drupalism. Why add another bump to an already steep learning curve?

#129

xtfer - July 2, 2009 - 03:24

As someone who has spent the last 2 years doing not much but editing themes, I'd like to see something that is simple to use, but allows me to extend it as necessary.

The idea of using a p() to call a render() seems the most logical.

I don't care whether I have to use p() or print, as long as I don't have to go and check the format of the function every time I use it (rules out 2 from #112). I also don't want to have to remember something arcane like print render() for every field (rules out 3). Additionally, having to use something like p(l(p($foo,TRUE),node,node-id) is ridiculous and will trip every themer who deals with it.

So +1 for Option 1.

I can use p($foo) whenever I like.
I can use render($foo) if i need it.

This is far and away the easiest suggestion for a themer, even if it does break the upgrade path a little (but when has that ever stopped Drupal in the past).

#130

dvessel - July 2, 2009 - 04:09

I read through this whole issue when I should have been working. Oh well.. I have coffee. :) Will stay up a little later.

Jacine pointed out yched's #112 comment but I wasn't aware that all the big pieces where in already. I don't completely agree with the direction it took but it's my fault for not spotting this sooner.

On to #112:

I don't like option #2 at all. It should either print or not. It seems really strange to my eyes passing in TRUE which is very non-descript. TRUE what? Anyone who works with this will eventually get it but it looks so generic which contributes to that initial hurdle when learning how to theme.

<?php
<div><?php p(l(p($content['field_url_title'], TRUE), 'node/' . $node->nid));
?>

?>

The above example posted by yched will be a common thing? $content['field_url_title'] is information that would never be printed on its own without any surrounding markup but themers will be expected to wrap it in a p() function anyway? I understand it's for consistency but that double p() wrapping is not easy to get. "p" stands for what now? Print? Or print when it doesn't get the secret message.? Now that doesn't seem consistent.

I like concise code but if we're going to have exceptions like the above example, I'll vote for no shortcuts just to prevent a "print" statement. So #3 has my vote.

#131

System Message - July 5, 2009 - 02:00
Status:needs review» needs work

The last submitted patch failed testing.

#132

EvanDonovan - July 5, 2009 - 17:12

As someone who's less adept at theming Drupal, I think #2 is quite confusing, with the nested single-letter functions and the obscure TRUE parameter. I see drawbacks to both #1 & #3, but overall, I think I favor #3, because print is standard PHP.

#133

webchick - July 5, 2009 - 23:57

There doesn't seem to be much support here for the p() wrapper. I'm leaning toward either leaving things as they currently stand (render does implicit print, for consistency with hide/show) or returning the value from render() and using print render(...); The designers/themers who've commented here seem slightly in favour of print render(...) from what I can see.

#134

Jeff Burnz - July 6, 2009 - 16:37

I'm probably the wrong person to comment because I think they are all great and personally quite like #2, but the inconsistency make me a little weary and I'd punt for #3 and ease of learning.

#135

Rob Loach - July 6, 2009 - 18:29

A large majority of templating systems out there (like Smarty or ETS) restrict the use of PHP within the templates and limit you to just outputing variables. This is because it forces a reduced amount of business logic within the templates, as well as helps with security. Due to this, if we were going to port Smarty theme engine or ETS theme engine to Drupal 7, we'd have to put preprocessing functions within smarty.engine to render the $content to obtain the rendered content rather then just having the content array.

Having known that, what are your thoughts on having a Drupal core theme engine (maybe named Starkly or something), that provides the themed variables as fully rendered elements rather then the element content arrays?

#136

moshe weitzman - July 6, 2009 - 18:53

@Rob - yes, I would support such a theme, though i think it should be phptemplate based. see studio theme for a fine example.

#137

Benjamin Melançon - July 10, 2009 - 11:38

Note that with #112 option 1, people who don't want to use p() don't have to. The reaction against it is from being made to use it / being talked down to.

Whatever happens, -1 for render secretly printing.

I agree with Jacine that p($title) is not necessary, and there is no need to hide the difference between complex and simple variables in core templates. But I think we should use

<?php
p
($var['part'])
?>
for
<?php
print render($var['part'])
?>
and if someone does want to use p() everywhere for consistency (or, conversely, put render() in pre-process functions and always use print $var) they most certainly can.

Nothing too hard to understand, and if we want Drupal to claim every single-letter function in PHP we could rename render to r making a dead-simple render/return function usable everywhere. The complex use case becomes:

<?php
p
(l(r($content['field_url_title']), 'node/' . $node->nid));
?>

#138

yched - July 10, 2009 - 22:20
Status:needs work» needs review

All in all I think we're not ready for p().
- it's (arguably) less visible in templates than a print that usually gets highlighted by IDEs
- We cannot make it generic: p('literal'); p(function_call()); p($conditional ? 'foo' : 'bar');p($var . 'baz'); break and still need to go through an old-school print, which really adds confusion.

Thus here's a patch for #112 option 3 (remove the print out of render(), everything that's printed in a template goes through an explicit print).

I kept the fact that render() accepts flat variables (simply returns them as-is), as a safe guard against render($title). "Don't baby sit broken code" applies differently when it's templater-code, I guess.

AttachmentSizeStatusTest resultOperations
render-followup-455844-138.patch5.18 KBIdlePassed: 11660 passes, 0 fails, 0 exceptionsView details

#139

redndahead - July 11, 2009 - 01:08

I think this is the best approach for now as it still leaves open the ability to create a p() if someone wants to champion the cause. I looked over the patch and it looks valid. I'll leave it to someone more vocal to set it as RTBC.

#140

emmajane - July 13, 2009 - 20:59

Today I needed to poke a stick at user-profile.tpl.php for unrelated reasons. I was greeted with:
<?php render($user_profile); ?>
and was *instantly* confused by the lack of "print."

With that in mind I am +1 for this latest patch which removes print from render(). Removing print also makes render() consistent with drupal_render() and drupal_render_form() and consistency is good.

Where possible I would like to see themers encouraged to move render() to template.php so that we can have print $rendered_var, which I think is the easiest to deal with. In other words, I don't want tpl.php files to end up looking like this again:
print theme('links', $primary_links, array('class' => 'links primary-links'))
(yes, this is already gone in D7, I'm just giving it as an example of something that is confusing)

#141

webchick - July 13, 2009 - 21:13
Status:needs review» needs work

Committed #138 to HEAD. Thanks! We can possibly revisit this later in the cycle, but for now agreed that this is more consistent.

Still needs upgrades afaik.

#142

redndahead - July 13, 2009 - 22:34
Status:needs work» fixed

Fixed docs http://drupal.org/node/254940#granular

I believe that was the last issue for now. If p() is revisited it probably should be a new issue.

#143

yched - July 13, 2009 - 22:46

Yay, thanks redndahead !

#144

System Message - July 27, 2009 - 22:50
Status:fixed» closed

Automatically closed -- issue fixed for 2 weeks with no activity.

#145

cpelham - July 29, 2009 - 21:17

Just read through the entire thread and as some who does a little of everything, code, theme, design, I can say that I understand (more or less) and support the final solution. I think it will be a great improvement!

 
 

Drupal is a registered trademark of Dries Buytaert.