Table of contents
The {embed}
tag in ExpressionEngine has had a rough couple of years. What used to be the only reasonably useful way to break your design up into smaller, reusable chunks has come under siege. For simply breaking up your templates or reusing bits of code, {embed}
has been partially replaced by snippets; meanwhile, the tag has been rightfully hammered for the performance hit it can cause (from Solspace’s performance guide):
For every single embedded template that is found, ExpressionEngine performs a query to retrieve that template and its settings. It then processes that template as if it was its own page and when finished it replaces the
{embed=""}
tag with the final, parsed output. Effectively, an embedded template is a sub-page within a page, and it requires all of the processing that building a page from a template entails. It is not a lightweight operation.
Basically, these days you shouldn’t use an {embed}
if you don’t have to.
But they’re still useful in one situation that comes up entirely more often than you might like: you have to nest a tag pair inside another tag pair (especially if it’s the same module or there are otherwise conflicts between the variable names). In that case, you pass some variables through the embed, probably like so:
{exp:channel:entries}
{embed='embeds/some_troublesome_tag'
entry_id="{entry_id}"
{!-- It's, like, always entry_id --}
}
{/exp:channel:entries}
Then in the embed template, you access that passed variable with {embed:entry_id}
and – voilà – no more trouble.
Enter Low Variables 2.3+
However, a fairly recent update to the excellent Low Variables add-on has pretty much eliminated that reason to use {embed}
, too.
In the 2.3 update, there was this change:
Added the preparse:my_var="" parameter to the Textarea variable type
With these instructions (emphasis mine):
If the variable content contains variables that need to be parsed before it is put in the template, you can use this parameter, similar to embed variables.
For real?! I’m in love.
Basically, you create a TextArea Low Variable, access it with the {exp:low_variables:single}
tag (so it’s parsed along with other {exp:tag}
s, and you can pass on anything you want:
{exp:low_variables:single
var='used_to_be_an_embed'
preparse:segment_2='{segment_2}'
}
A recent example
I’ll give an example I ran into recently on the site that I maintain:
I had an overly-complicated scheme with a Playa relationship tag nested inside a module that checked some stored information about the logged-in member (that needed a url_title
) nested inside another Playa variable nested inside a {exp:channel:entries}
loop.
{exp:channel:entries}
{!-- this is a Playa variable.
basically like using exp:playa:children --}
{relationship}
{exp:member_categories:check category="{url_title}"}
{exp:playa:children entry_id='{entry_id}'}
[some code about the grandchild entry]
{/exp:playa:children}
{if no_results}
[some more code…]
{/if}
{/exp:member_categories:check}
{/relationship}
{/exp:channel:entries}
Yikes. I had potential conflicts in entry_id
, url_title
, and {if no_results}
, and I was nesting Playa in Playa on top of that. Before Low Variables, I definitely needed an embed to untangle it.
{exp:channel:entries}
{relationship}
{embed='spaghetti/category_check_and_playa'
child_entry_id='{entry_id}'
child_url_title='{url_title}'
}
{/relationship}
{/exp:channel:entries}
That would pass the variables as you’d expect into the embed template:
{exp:member_categories:check category="{embed:child_url_title}"}
{exp:playa:children entry_id='{embed:child_entry_id}'}
[some code about the grandchild entry]
{/exp:playa:children}
{if no_results}
[some more code…]
{/if}
{/exp:member_categories:check}
That worked! All the tags got the parameters they needed when they needed them, and there were no surprises with variable conflicts.
But you can do the same thing with a Low Variable in place of the embed:
{exp:channel:entries}
{relationship}
{exp:low_variables:single
var='my_spaghetti_code'
preparse:child_entry_id='{entry_id}'
preparse:child_url_title='{url_title}'
}
{/relationship}
{/exp:channel:entries}
using a textarea Low Variable named my_spaghetti_code
:
{exp:member_categories:check category="{child_url_title}"}
{exp:playa:children entry_id='{child_entry_id}'}
[some code about the grandchild entry]
{/exp:playa:children}
{if no_results}
[some more code…]
{/if}
{/exp:member_categories:check}
(exactly the same as the embed, but the early-parsed variables aren’t prepended with embed:
)
And it works too!
The nerdy bits
This works because of how the parser goes through a template in sequence and parses module/extension tags in the order they’re encountered. Let’s go step-by-step.
At the {exp:tag}
phase, the parses sees this:
{exp:channel:entries}
{relationship}
{exp:low_variables:single
var='my_spaghetti_code'
preparse:child_entry_id='{entry_id}'
preparse:child_url_title='{url_title}'
}
{/relationship}
{/exp:channel:entries}
It processes the channel:entries
tag first (including the {relationship}
Playa pair’s {entry_id}
and {url_title}
variables), leaving this (say for 2 entries):
{exp:low_variables:single
var='my_spaghetti_code'
preparse:child_entry_id='1'
preparse:child_url_title='first-related-url-title'
}
{exp:low_variables:single
var='my_spaghetti_code'
preparse:child_entry_id='2'
preparse:child_url_title='second-related-url-title'
}
Now the parser tackles the first low_variables:single
. The result replaces the tag with the contents of the variable (and the preparse:variable_name
variables with the parameter values):
{exp:member_categories:check category="first-related-url-title"}
{exp:playa:children entry_id='1'}
[some code about the grandchild entry]
{/exp:playa:children}
{if no_results}
[some more code…]
{/if}
{/exp:member_categories:check}
{exp:low_variables:single
var='my_spaghetti_code'
preparse:child_entry_id='2'
preparse:child_url_title='second-related-url-title'
}
And now, continuing down the template in order, the parser sees the member_categories:check
and playa:children
tags as if they were there all along! First the outer member_categories:check
(we’ll say it had a result):
{exp:playa:children entry_id='1'}
[some code about the grandchild entry]
{/exp:playa:children}
{exp:low_variables:single
var='my_spaghetti_code'
preparse:child_entry_id='2'
preparse:child_url_title='second-related-url-title'
}
And then the playa:children
tag:
[now totally processed code about the grandchild entry]
{exp:low_variables:single
var='my_spaghetti_code'
preparse:child_entry_id='2'
preparse:child_url_title='second-related-url-title'
}
Repeat for the next low_variables:single
tag. Clever!
Performance gains
Although it ends up working almost just like the embed, we’ve lost the whole “run through the template parser again” overhead. How much of a difference does that make, you ask?
I ran a test with real data from the site, using a template with only the above code and with the outermost channel:entries
tag going through every applicable entry. That produced about 200 relationships, which in turn left about 200 embeds (or low_variables:single
tags) to chug through.
I ran that only partially unrealistic mess with the Output Profiler, tallied the processing time, and averaged it over 50 refreshes:
Tag | Average processing time |
---|---|
embed
|
1.87s |
low_variables:single |
.67s |
There you have it: a small {embed}
is almost 3 times slower than an identical {exp:low_variables:single}
tag.
For just a few tags you might not notice, but it definitely adds up.
Some gotchas
If you’re going to replace embeds this way, some things to note:
- As with all template debugging, don’t even try something like this unless you thoroughly understand parse order. Lodewijk Schutte (who else) has you covered with a presentation and a cheat sheet (.pdf) in addition to occasional blog posts. All the following bits come from understanding the parse order.
- If your Low Variable uses any early-parsed variables (
{segment_1}
,{embed:name}
, early globals), those have missed being parsed so you’ll have to pass those in withpreparse:name
(likepreparse:segment_2='{segment_2}')
- Likewise, you won’t see performance gains from “simple conditionals” parsing early (because you’ve missed that boat too). They’ll still work themselves out, but at the later “Advanced conditionals” parsing phase.
- If you need some PHP parsed on input in your variable, that’s not going to happen. And also you’re nuts. PHP on output will be OK. (I try to avoid PHP in templates like the plague, but Low presents some compelling uses here if you’re interested.)
Nice knowing you, {embed}
This is just one of the many ways that Low Variables lets you tinker with tags to your benefit. It’s a well-loved add-on with a lot of potential uses and this is icing on the cake.
Just don’t step on {embed}
’s corpse on the way out.