Guide 02 · Developer docs

Template languages: Smarty and Twig

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

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 seeCause
A visitor's input rendered as markup: a search term with a tag in it shows as bold textA 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> literallyA value that was already HTML was escaped
&amp; on the pageA 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:

SmartyTwig
Keep the caller's variables outNot possible: a partial always sees themonly, 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 blockVisible to the base after that block has renderedNever visible to the base as a variable; its stored copy is
A partial reading a variable it was not passedPicks up any variable of that name the caller hasThe 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

SituationSmartyTwig
An undefined variablePrints nothing, and writes a warning to the site's log with the template's namePrints nothing
A method the record does not offerError: Method … does not exist in …Prints nothing
An attribute that is neither a getter nor a layout areaError: Property … does not exist in …Prints nothing
A method called on nullError: Call to a member function … on nullPrints nothing
A Mailer email's subject or bodyNot rendered as a template; placeholders replacedStrict: 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.

Go deeper

Was this page helpful?

Your answer goes to the docs team.