Skip to content
V
For developers
Architecture, conventions, the core and security
UI layer / Latte templates

Latte templates#

This chapter is about the PHP side of templates: what the system has added to Latte, how to write your own filter, and how snippets and signals hold together.

What belongs where

How a template is split into files, how the system picks a layout and what flows into a template from the presenter — that is in the section for front-end coders: Splitting a template into Latte files. Here you will find only what a developer adding something to templates from PHP needs.

Custom extensions#

app/Core/Latte/Extensions/ — nine extensions, each adding filters, functions or tests:

Extension What it adds
Url working with addresses
Date date and time formatting
Price price formatting by currency
Email, Phone contact formatting
String text adjustments (e.g. replacing blacklisted words)
FormPair rendering a label–field pair
SafeLink the safeLink() function — a link that does not fail on a missing target
Theme themeAsset(), themeUrl(), the isTheme() test

Plus three macros in app/Core/Latte/Macros/: MultiControlMacros, SafeLinkMacro and ThemeMacro.

Procedure: adding my own filter#

  1. Find out whether it belongs in an existing extension. Price formatting into PriceLatteExtension, text handling into StringLatteExtension.
  2. If not, create a new extension in app/Core/Latte/Extensions/, extends Latte\Extension, and implement getFilters() / getFunctions() / getTests().
  3. Write nothing into the configuration. The extension is found by itself: LatteExtensionsCompiler walks the Extensions/ and Macros/ folders with the robot loader, registers every instantiable class extending Latte\Extension and attaches it to LatteFactory.
  4. Put the logic in a helper, not in the closure's body — a filter should be a one-line redirect.
  5. Verify it in both sections if the filter is meant to work on the front end and in the administration.

Formatting belongs in a filter, not in a template

A currency condition in the middle of a template will appear in fifteen places within a year — and be forgotten in fourteen of them when it is fixed.

A new extension only takes effect after the container cache is cleared

Registration runs while the DI container is compiled. Until temp/cache is deleted the filter does not exist and Latte reports an unknown filter — which looks like a typo in the name.

Snippets and their PHP counterpart#

A snippet's name is also the argument to redrawControl(). The convention: a short lowerCamelCase name, Wrapper shortened to Wrap, and the wrapping area named after the inner snippet plus Area.

{snippetArea linksSnippetArea}
    {snippet linksSnippet}
    {/snippet}
{/snippetArea}
$this->redrawControl('linksSnippet');

A snippet's name must be a LITERAL

A name composed from a variable is not registered and redrawControl() then silently does nothing. No error is printed — the page simply is not redrawn.

AJAX redraws only the INSIDE of a snippet

The attributes of the element carrying the snippet stay stale — the class and any data- attribute alike. When a redraw is supposed to change a state expressed by a class, that class has to be inside.

{var} inside a {snippet} has its own scope

A variable defined outside the snippet may not exist inside it after a redraw — during an AJAX redraw only that snippet is rendered, not the template around it.

Redrawing blindly is not the way

redrawControl() without an argument redraws everything. Always name the target — otherwise snippets that changed for an unrelated reason end up in the response too.

In detail: Components and Code conventions.

Translations in a template#

Three different behaviours depending on where you are — a Front presenter needs {translator $translatorDomain}, an Admin presenter and components do not. It is the most common source of "why did it not translate" and has a chapter of its own: Translations.

A missing translation in a route can bring down a whole language version

Not an empty space — a page error. It concerns the texts an address is composed from, not labels. In detail: Translations, the section on the database layer.

Reserved variable names#

Some names are taken by the template system

$form, $flashes, $control, $presenter, $lang, $locale, $theme and others. Overwriting one surfaces not as an error but as strange behaviour — typically as something entirely different breaking on the same page.

Debug mode and AJAX#

A deprecated call in debug mode breaks the AJAX response

Tracy adds output to the response that breaks the JSON. It surfaces as "AJAX does not work" — with no warning and no console error, because the response simply cannot be parsed.

Where to look#

I want Where
the extensions and their filters app/Core/Latte/Extensions/
the macros app/Core/Latte/Macros/
the automatic registration app/Core/DI/LatteExtensionsCompiler.php
the engine settings config/Shared/latte.neon
the structure of templates and layouts Splitting a template into Latte files

Follow-up chapters: Components · Forms · Translations · Front-end themes