John D Wells

I build websites with HTML, CSS, jQuery, PHP, ExpressionEngine & CodeIgniter.

Proud co-founder of the kick-ass, London-based creative agency One Darnley Road.

© johndwells

#EECMS Parse Order Be Damned: Advanced Conditionals as Tag Parameters

Once again I’m shaking my fists at the impossible hoop-jumping gauntlet that is the ExpressionEngine Parse Order. This time I want to pass the result of an Advanced Conditional as a value to a module tag parameter. It’s been long said it can’t be done. It’s time to change that.

Published:
Mar 23, 2013
Filed under:
EECMS
Comments:
11 - add yours

Nesting an Advanced Conditional inside a module tag parameter. It's been said it can't be done.

{exp:channel:entries
    status="{if '{segment_3}' == 'closed'}closed{if:else}draft{/if}"}
    {title} - {status}<br>
{/exp:channel:entries}

"You are using advanced conditionals in your tag, and in EE, they don't parse soon enough for your tag. Your tag is likely seeing all possible conditions when it processes."
Using conditionals in channel entries parameters

Ultimately at issue here is the ExpressionEngine® Parse Order, which is an obscure yet critical concept that every EE developer will at some point confront (and with the help of fine folks like Low, hopefully overcome).

On most days it sits quietly behind the scenes making things Just Work™ and providing the backbone for the simplicity and beauty of EE's template markup.

Then every so often it is the devil-in-the-details gremlin that riddles your project with exceptions, caveats, and "it depends" workarounds - like when it comes to conditional statements.  See, in the world of the Parse Order, no two conditionals are the same - they're either considered "Simple", or "Advanced", and where they sit amongst the Parse Order matters a great deal - especially for performance, but also when we want to use them as a tag parameter.

Simple Conditionals - unfashionably early:

A conditional is considered "simple" if it is evaluating variables that are already available by the time the template parsing engine reaches the simple conditionals parsing stage (e.g. segment, embed, and user-defined global variables), the expression evaluates a single variable (i.e. contains no logical operators such as OR, AND), and the conditional does not make use of the else or elseif control structures.

In short, a simple conditional will look very much like this:

{if segment_1 == "joe"}
    <h1>Hi Joe!</h1>
{/if}

Advanced Conditionals - fashionably late:

Any conditional that isn't a simple conditional is considered an "advanced" conditional and is evaluated much later in the template parsing order. Advanced Conditionals can use logical operators (ex: OR, AND) to compare multiple variables to multiple values.

{if username == "joe" OR username == "jane"}
    <h1>Hi {username}!</h1>
{if:else}
    <h1>Who goes there?</h1>
{/if}

So when EE serves up each template, the Template Engine makes multiple passes through its contents, as it incrementally steps through each stage of the Parse Order. When it comes to evaluating conditional control structures, you need to know that:

  • Simple Conditionals are processed early, before module & plugins tags
  • Advanced Conditionals are processed late, after module & plugin tags

The takeaway from this is that you can use a Simple Conditional as part of a tag parameter, since the conditional will be evaluated prior to the outer tag being run. However since Advanced Conditionals are parsed late, the outer tag's parameter will equal the complete, un-evaluated Advanced Conditional string.

So, what else can we pass as a tag parameter?

That is a great question. We now know we can pass simple conditionals, and referencing the Parse Order we know that we can pass early-parsed variables (segments, embeds and early globals) and preload_replace variables. So how about another plugin?

Yes, in fact - you can pass a plugin as a tag parameter. If you add parse="inward" to your outer tag, then the Template engine will check if the content of any parameter value appears to be itself a plugin - and if so, it will be parsed immediately. See:

/* ---------------------------------
/*  Plugin as Parameter
/*
/*  - Example: channel="{exp:some_plugin}"
/*  - A bit of a hidden feature.  Has been tested but not quite
/*  ready to say it is ready for prime time as I might want to 
/*  move it to earlier in processing so that if there are 
/*  multiple plugins being used as parameters it is only called
/*  once instead of for every single parameter. - Paul
/* ---------------------------------*/

if (substr_count($this->tag_data[$i]['tag'], LD.'exp') > 1 &&
    isset($this->tag_data[$i]['params']['parse']) &&
    $this->tag_data[$i]['params']['parse'] == 'inward')
{
....
$this->log_item("Plugin in Parameter, Processing Plugin First");
....
}

That, dear friends, is straight from the Template Engine itself - around line 1054. Hi, PaulBy now, I'm hoping you're with me on this ah-hah moment - because all we need now is a plugin that is specifically designed to evalute Advanced Conditionals. And lo and behold, one already exists.

The ifElse plugin from Mark Croxton

You may already know @croxton from Stash. Or from Switchee. Or other handy add-ons - you should check him out on Github. He's a mighty clever chap.

ifElse: Early parsing of advanced conditionals in EE templatesWith ifElse, Mark has once again done the hard work to make our lives easier - he's built a plugin that coaxes EE's Template Engine to parse an advanced conditional as if it were a Simple Conditional - which is to say the conditions are evaluated before evaluating the tags within each control structure. But as an added bonus, ifElse now gives us the ability to pass an Advanced Conditional as a tag parameter.

So if we add parse="inward" to our outer tag, and wrap our Advanced Conditional in the ifElse plugin, this is what we get:

{exp:channel:entries
    limit="3"
    parse="inward"
    status="{exp:ifelse}{if '{segment_3}' == 'closed'}closed{if:else}draft{/if}{/exp:ifelse}"}
    {title} - {status}<br>
{/exp:channel:entries}

Update 2nd April: As has been astutely pointed out a few times now, this is a nonsensical example that is better achieved with a Simple Conditional, since it is operating upon an Early Parsed variable. Keep in mind however that not all variables are parsed early, so there are still going to be times where an Advanced Conditional is unavoidable. My apologies for not choosing a more appropriate example from the outset.

And it Just Works™ once again - exp:ifelse is run, your condition is evaluated, and either 'closed' or 'draft' is returned to the status= parameter. It was everything we'd ever hoped for, and more. Horray!

Horray indeed! But before you go, some parting advice

Nesting tags within tags within tags can be taxing for EE's Template Engine to know where one thing starts and another begins. So adhere to these two rules if you use this approach:

1. Use double & single quote nesting with care

If you're using double quotes for your outer tag parameters, use single quotes for you nested plugin-tag-as-parameter. Using all of one quote type may yield unexpected results.

2. For this technique to work, be sure to wrap early-parsed variables in curly brackets.

Early-parsed variables (as well as preload_replace variables) should be contained in curly brackets (e.g. {variable}) to be sure the Template Engine can evaluate them early.

Note that for Simple Conditionals it is important to NOT wrap early-parsed variables in curly brackets. Thanks Paul for the reminder.

I should also say that I've only just discovered this trick, so I'd appreciate hearing feedback on your experiences, in particular if there are any other important caveats or syntax/parse rules to keep in mind. Happy templating all...

Update 1st April: As Low very correctly pointed out in the comments below, this does rely on an unofficial "hidden" feature, so there is no guarantee that future versions of EE will allow this technique. As always you should choose the solution that best suits the task at hand, and future-proof & maintainability are vital criteria to consider. 

Published:
Mar 23, 2013
Filed under:
EECMS
Comments:
11 - add yours
 

Have your say...

Great article, thanks a lot! Never thought of using {exp:ifelse}. Interesting.

However, I would strongly suggest adding that enabling *Template Debugging* in EE can save you *a lot* of troubleshooting by quickly telling you if that conditional/plugin you’re using in another parameter is being parsed on early enough or not.

This tool can reduce your troubleshooting time from hours to a few minutes, if not seconds!

  • #2
  • On 10:37 PM, 31/03/13
  • From São Paulo, BR
  • Robson Sobral said:

Hi, John!

Why not to use two simple conditionals? Isn’t easier?

{exp:channel:entries status=”{if segment_3 == ‘closed’}closed{/if}{if segment_3 != ‘closed’}draft{/if}”}

  • #3
  • On 11:05 PM, 31/03/13
  • From London, UK
  • John D Wells said:

Robson - If you only needed to compare against early parsed variables, then yes you could (in some instances) accomplish your goal with multiple Simple Conditionals side by side as you’ve outlined. But, not all variables are early - even preload_replace variables for example cannot be part of a Simple Conditional.

Nicolas - great point about using Template Debugging as a troubleshooting tool to determine if something is being parsed in time. I’ll try that next time, I’m sure it will help!

  • #4
  • On 07:52 AM, 01/04/13
  • From Leiden, the Netherlands
  • Low said:

The Simple Conditional example you gave is in fact an Advanced Conditional. The {username} variable is not a Segment var, Embed var or Snippet, but a late parsed “standard variable”, making it an advanced conditional, even though it looks like a simple one.

Secondly, I’d be very careful with using a plugin as a parameter value. It may work, but as Paul mentions in the code comment: “it’s not ready for prime time”. Meaning it’s not supported and could be pulled by EllisLab without notice (which they’ve been known to have done before). Therefore, I think it’s best not to rely on this method. Not too much, anyway.

If you need a parameter value based on an advanced conditional, you might be better off rethinking your strategy. Use multiple simple conditionals, use a custom plugin, or even some PHP on Input. Might be more work, but, in my opinion, will be more reliable in the long run.

  • #5
  • On 08:41 AM, 01/04/13
  • From London, UK
  • John D Wells said:

Thanks for your input Low - great catch about the simple conditional in fact not being simple at all.  I’d actually taken that straight from EE’s example docs and didn’t even consider that the username was in fact a late global. I’ll update with a correct example.

I appreciate the warning to not become too reliant upon it, in case future versions of EE break behaviour.  I didn’t think that using a plugin as a parameter was particularly new information, I was under the impression that plenty of people use it these days.

Anyway for me, I’m willing to take the risk. It’s produces clear, readable code without having to resort to custom plugins (which I love don’t get me wrong), running PHP on input (which I love less but completely appreciate), or embeds (which I avoid where possible).

  • #6
  • On 03:56 PM, 01/04/13
  • GDmac said:

I did use stash to store a value and use the plugin as parameter trick to output the stash value as a parameter value. You mention “taxing for EE’s Template Engine to know where one thing starts and another begins”. Another “tax” (why i avoid this technique), is that for every plugin found in a parameter, the whole template library class is instantiated another time, including calling the parse and processing tags methods. I currently rather use two real simple conditionals or switchee. Always check the template debugger if really only one of the tags is called.

  • #7
  • On 04:32 PM, 01/04/13
  • From London, UK
  • John D Wells said:

HI GDmac - Good point about additional processing overhead. It doesn’t look to be *quite* as bad as an {embed}, but every additional byte counts.

  • #8
  • On 05:32 PM, 01/04/13
  • From Undisclosed
  • Paul Burdick said:

Read this yesterday but only getting a chance to reply this morning. I see that most of the obvious issues have been caught in the comments.

The Template parser excels at both confusion and manipulability.  A great deal of the muddledness can be directly attributed to the decision made early on to have multiple levels of caching built in *and* that certain global variables (ex: username, ip_address) should never be cached.  Also, it was written over 9 years ago before PHP was fast enough to build a truly token based parser. It’s history sort of damns it.  At the same time, its complexity also allows quite a bit of freedom to manipulate it into doing what you need, as most of you have proven time and time again.

To reiterate a few points.

- Segments. With cleverness and the nesting of simple conditionals, you can often make normally advanced conditionals involving them and embed variables into simple ones.

- Global variables.  What you have linked is NOT a global variable that is parsed early. Those variables are parsed AFTER tags.  The global vars that are parsed early are the ones taken from the config array or index.php file.  Also, that array is used by addons like Low Variables and Template Morsels.

- The Tag as Parameter Plugin Trick.  It does create another instantiation of the Template parser so it does add a bit more load, but PHP 5.2+ has become extremely efficient with object handling. I myself have used this technique numerous times on client sites, and I have seen it often in the wild from other developers. It would not be my first choice as a solution to a problem, but I am definitely not afraid to use it. Also, that code has been in there for at least 6 years, so it would be odd if EllisLab decided to remove it now.

- Wrapping Early Parsed Variables in Brackets.  I expect you mean solely for advanced conditionals.  Because I suspect that approach will likely break simple conditionals.

All in all, it would be great to see a new Template parser that is more logical than the current one. Obviously, this old one cannot go away though as we are all using and abusing it in ways that no one could portend nearly a decade ago.

  • #9
  • On 06:33 PM, 01/04/13
  • GDmac said:

Thanks paul for some history and thoughts added to the story. the “draft” vs “closed” example can also be done with two simple conditionals, no plugin needed.

param=”{if segment_3==‘closed’}closed{/if}{if segment_3 != ‘closed’}draft{/if}”

Another trick comes from Low and is about simple conditionals being parsed before preload_replace tags AND the fact that only the first preload_replace tag with the identical name is used (because it will replace them immediately in the template).

{if segment_3 == ‘closed’}
{preload_replace:preload_var=‘closed’}
{/if}
{preload_replace:preload_var=‘draft’}
...
parameter=”{preload_var}”

  • #10
  • On 11:41 AM, 02/04/13
  • From London, UK
  • John D Wells said:

Paul, thanks so much for your perspective and insight (and corrections).  I’d wondered how long this “hidden feature” had been in place, so good to know it’s been there a long while.  And it’s also a good reminder to appreciate the origin of the Template parser’s roots. I’ll try not to give it such a hard time!

I’ll make a few updates to the above to be sure I’m not misleading anyone as to best practices etc.

Cheers,
John

  • #11
  • On 11:53 AM, 02/04/13
  • From London, UK
  • John D Wells said:

GDmac - I suppose I’ve chosen poor examples of Advanced Conditionals, since they are acting upon Early Parsed variables and therefore there is potential for a Simple Conditional alternative.  But swap these out for Late Parsed variables (or plugin/module tag variables), and the Simple Conditional argument is moot - point being, Simple Conditionals are not always an option.

The preload_replace approach of Low’s that you mention is fantastic and I’ve used it before. I especially like it because it’s “core” behaviour and doesn’t rely on any add-ons.

Cheers,
John