Core dna renders server-side sites with Smarty or with Twig, one engine per site, and treats the two as peers. This guide is the one place both are documented: what they share, each engine's language and the platform's additions and limits, the two side by side, and translation.
L1–L2
Unit 04 · Comparing and porting
Behaviour that differs between the engines
Comparing and portingPage 20 of 25Updated
The differences between Smarty and Twig on Core dna that cause visible bugs beyond syntax: escaping, which is off in both, what an include sees, what an undefined variable or a missing method does, and how compiled templates behave on production, with the symptoms of each and how to test for them.
Who this is for. The developer moving between engines, reviewing a port, or debugging a template that behaves differently from the same template in the other engine. Afterwards you can predict each difference and tell its symptoms apart.
Escaping: off in both
The usual comparison says Twig escapes by default and Smarty does not. On Core dna neither does: the platform switches Twig's autoescaping off, so {{ value }} prints exactly what {$value} prints. The rule is therefore the same for both engines:
Escape everything that came from a visitor, a URL, a feed or a plain-text area: {$q|escape}, {{ q|e }}, with the right strategy for a URL or an attribute.
Print a richtext area as it is; it holds HTML on purpose.
Escape once, last.
The difference is in habits. A developer from a Twig project elsewhere expects escaping to happen and writes none; a template ported from such a project carries no |e at all. A developer who has been told “Twig escapes” adds |raw where it is not needed, which here does nothing. Three symptoms tell you the rule has been broken:
You see
Cause
A visitor's input rendered as markup: a search term with a tag in it shows as bold text
A value was printed without escaping. This is the cross-site scripting bug, and the one to fix first
Tags shown as text: a richtext area prints <p> literally
A value that was already HTML was escaped
& on the page
A value was escaped twice, often once where it was stored and once in the template
Smarty's |escape also encodes the ampersand of an entity that is already there; |escape:'html':'UTF-8':false leaves existing entities alone.
What an included partial sees
By default the two engines behave alike: a partial sees all of its caller's variables, and a variable it assigns stays inside it.
Template code
{* the caller *}
{$heading = 'Our services'}
{include 'partials/title.html' level=2}
{$seen|default:'not visible here'}
{* partials/title.html: sees $heading and $level *}
<h{$level}>{$heading|escape}</h{$level}>
{$seen = true}
{# the caller #}
{% set heading = 'Our services' %}
{% include 'partials/title.html' with {level: 2} %}
{{ seen ?? 'not visible here' }}
{# partials/title.html: sees heading and level #}
<h{{ level }}>{{ heading|e }}</h{{ level }}>
{% set seen = true %}
The differences are at the edges:
Smarty
Twig
Keep the caller's variables out
Not possible: a partial always sees them
only, or with_context = false on the function. The underscore helpers still reach it, as globals
Get a value back out
{$seen = true scope='parent'}, or scope='global'
Read the stored copy: _templateContext.get('seen')
A value set inside a child's block
Visible to the base after that block has rendered
Never visible to the base as a variable; its stored copy is
A partial reading a variable it was not passed
Picks up any variable of that name the caller has
The same, unless the include is only
A partial that behaves differently depending on where it is included is almost always reading a variable it was not passed. In Smarty, pass it explicitly; in Twig, add only and the missing parameter shows at once as an empty value. For values that must cross templates, _templateContext behaves the same in both, which is why it is the portable choice.
Undefined variables and missing methods
Situation
Smarty
Twig
An undefined variable
Prints nothing, and writes a warning to the site's log with the template's name
Prints nothing
A method the record does not offer
Error: Method … does not exist in …
Prints nothing
An attribute that is neither a getter nor a layout area
Error: Property … does not exist in …
Prints nothing
A method called on null
Error: Call to a member function … on null
Prints nothing
A Mailer email's subject or body
Not rendered as a template; placeholders replaced
Strict: an undefined variable fails the render, and the platform falls back to placeholder substitution
How to test for it:
Smarty: a page with an error does not render, so it shows the first time anyone loads it. Undefined variables do not, so check the site's log for template warnings after a release, and use |default or isset() where a value is optional.
Twig: nothing shows, so load each template with representative content and look for gaps. Use ?? , |default and is not empty where a value is optional, and check method names against the record's list when something is blank (Seeing the list).
Compilation and caching
Both engines keep compiled templates in the site's .cache folder on production, Smarty as flat files and Twig in two-letter subfolders, and the same actions clear both: a deploy through version control, or Clear Cache on the environment. The difference is what happens to a template file changed on production without either:
Smarty compares the file with its compiled copy on each request and recompiles a changed one.
Twig does not check, and serves the old compiled copy until the cache is cleared.
On any other environment both recompile on every request. Compilation and caching has the details and the page cache, which behaves identically in both.