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 02 · Smarty
Smarty inheritance, includes and the template context
SmartyPage 11 of 25Updated
How Smarty templates are composed on Core dna: a base template and the blocks a child fills, with append, prepend and hide; includes with parameters and their scope; the platform's _templateContext for state that crosses templates, such as a heading level; and what {strip} does to markup.
Who this is for. The developer building a Smarty site's page templates and partials. Afterwards you can set up a base template, fill and extend its blocks from a child, pass values into partials without collisions, and carry state across templates that do not share variables.
A base template and a child
The base template defines the page once and marks the parts a page may change with {block}. A child starts with {extends} and fills the blocks it needs; every block it leaves alone keeps the base's content.
Template codetemplates/includes/frontend/base-template.htmlSmarty only
Template codetemplates/pages/pages/article.htmlSmarty only
{extends 'includes/frontend/base-template.html'}
{$bodyClass = 'article'} {* top level: reaches the base everywhere *}
{block name='title' prepend}Article: {/block}
{block name='head' append}<meta name="robots" content="index, follow">{/block}
{block name='promo'}Read the full series{/block}
{block name='content'}
<h1>{$page->getName()|escape}</h1>
{$page->body}
{/block}
In the child
Effect on the base's block
{block name='x'}…{/block}
Replaces it
{block name='x' append}
Adds after the base's content
{block name='x' prepend}
Adds before it
{$smarty.block.parent} inside the block
Prints the base's content at that point
hide works the other way round and belongs in the base: a block marked hide renders only when a child fills it, and {$smarty.block.child} places the child's content inside the base's wrapper. A page that does not fill promo above gets an empty <aside>, not an empty promotion box.
Where an assignment sits decides whether the base sees it:
At the top level of the child, before or after {extends} and outside any block: the base sees it everywhere, including above its first block. That is where $bodyClass goes.
Inside a child's block: the base sees it only after that block has rendered. A value set in content is empty in the <head> and present in a footer below content.
Anything else at the child's top level, such as stray markup, is discarded. Twig refuses the same template.
For a value that must reach components rendered with getHtml(), or that is set in one place and read before it in the document, use the template context. Set up a base template and extend it walks through a full base template in both engines.
Includes and their parameters
{include} renders another template in place. Attributes after the name become variables inside it, and the included template also sees every variable of its caller. A parameter wins over a caller's variable of the same name, inside the include only.
Template codeSmarty only
{include 'elements/card.html' title=$post->getName() url=$post->getFullPath() level=3}
{* capture the output instead of printing it *}
{include 'elements/card.html' title=$post->getName() url=$post->getFullPath() assign='cardHtml'}
{* a component that includes another component *}
{include 'components/button.html' label='Read more' href=$url ignoreComponent=true}
Four rules for includes on Core dna:
Shared scope is the default, and the main source of collisions. A partial that reads $title without being passed one picks up whatever $title its caller has. Pass everything a partial needs as a parameter, and give partials names that say they are partials. Smarty has no way to give an include only its parameters; Twig's only does that.
A component inside a component sees the outer $component. A component template that reads its areas from $component when one is set will read the outer component's areas when included by another. Pass a flag such as ignoreComponent=true and test it, or render the inner one with getHtml(), which gives it its own $component.
Assignments inside the partial stay there unless it uses scope (Assignment and scope).
A path built from a variable must exist. A template cannot check for a file first, and an include of a file that is not there stops the render with Unable to load template. Keep the possible values to a fixed list, and fall back in the template before the include.
Shared partials go under the folders the platform's own templates use: includes/ for chrome such as the header and footer, elements/ for small reusable pieces, and a partials/ folder beside the templates that share them. Names are always full paths from templates/ (Full names and relative paths).
The template context
$_templateContext is one store shared by every template rendered for the request: the page template, its base, its includes, and every component and record rendered with getHtml(), which otherwise see none of the variables a template assigned. set(key, value) stores, get(key, default) reads, and set() prints nothing. It has two common uses.
Tracking a heading level through nested components
A section component does not know how deep it sits. It reads the current level, uses it, and moves its children one level down for as long as they render:
Template codetemplates/components/section.htmlSmarty only
Components on a page each need a script, and the base template should load each one once, at the end of the page. Each component adds its name; the base prints the list after content has rendered:
Template codeSmarty only
{* in a component *}
{$scripts = $_templateContext->get('scripts', [])}
{$scripts[] = 'gallery'}
{$_templateContext->set('scripts', $scripts)}
{* in the base template, below the content block *}
{$scripts = array_unique($_templateContext->get('scripts', []))}
{foreach $scripts as $bundle}
<script src="/dist/js/{$bundle}.js" defer></script>
{/foreach}
The order of rendering is the order of the document, so the base must read the list below the block that fills it. A value read before it is set comes back as the default.
{strip} and whitespace
{strip}…{/strip} removes the indentation at the start of each line and the line breaks between lines, so a block of nested markup comes out on one line. Production templates wrap their logic-heavy blocks in it to keep generated markup compact and free of stray gaps between elements laid out side by side.
The case where it changes what the visitor sees is inline content split across lines. Text or inline elements on separate lines lose the space the line break gave them: Read and more on two lines become Readmore, and two links on two lines touch. Put inline content on one line, or print the space explicitly with {' '}.