Hi! 👋
I spent three years writing Twig before I met Latte. Two things made me switch, and they are the two things this course is built around.
The first one I felt every single day. In Twig, string concatenation is ~. In PHP it is .. I never once managed to keep that straight, and not because it is hard - because I had spent the morning in PHP, where it is ., and my fingers had opinions. Same with {% set x = 1 %} versus $x = 1, with {{ user.name }} that might be a property or a getter or an array key (Twig decides at runtime, you find out later), with filter names that almost match the PHP functions but not quite. Every one of those is a tiny context switch. Individually trivial, a hundred times a day, in a language you are not writing in the other window.
Latte does not have a language. Inside the braces you write PHP - $user->name, $a . $b, ??, match, arrow functions, named arguments. Nothing to look up, nothing to switch to. I was productive in an afternoon, and I have never since typed the wrong concatenation operator.
The second one took longer to appreciate, and matters more. Every engine escapes by default these days - Twig and Blade included - and every one of them calls htmlspecialchars() and considers the job done. But escaping is not one job. A value in HTML text, in an unquoted attribute, inside <script>, and in a href need four different treatments, and one function cannot be right in all four. Naive auto-escaping is not safety; it is the feeling of safety, which is worse.
Latte parses the HTML. It knows which of those four places your value is landing in and escapes it accordingly, every time, without being asked. It is the first and still the only PHP template engine that does this.
Do not take my word for it - take the XSS quiz: nine snippets, and the question is which ones you would have escaped correctly by hand. I got six, after years of writing PHP for a living. The three I missed were not exotic.
So: small programs, real output, one idea at a time. Let's go.
PHP 8.4 or newer.
git clone https://github.com/nette-examples/latte-by-example
cd latte
composer installRun the first chapter:
php 01-hello-world/example.phpIt prints HTML straight to your terminal, so you can read it immediately (or pipe it into a file and open it in a browser - your call). Then open that chapter's readme.md, which walks you through what just happened, and finish with Try it yourself. Those exercises are the part that actually sticks; everything before them is me talking.
Chapters build on each other, so go in order. I promise never to use something in the code that I have not explained yet.
Want to poke at Latte without cloning anything? fiddle.nette.org runs it in the browser.
| # | Chapter | What it covers | Level |
|---|---|---|---|
| 01 | Hello World | The engine, the cache directory, {$var} - and why not to just write <?= $x ?> |
Beginner |
| 02 | Expressions and Filters | PHP expressions in tags, {var}, {default}, filters, {=…}, nullsafe ?| |
Beginner |
| 03 | Conditions | {if}, the in operator, the short ternary, and keeping the output clean |
Beginner |
| 04 | Loops | {foreach}, $iterator, {sep}, {first}, {last}, {skipIf} |
Beginner |
| 05 | n:attributes | Tags as HTML attributes, n:class, n:tag-if, n:ifcontent |
Beginner |
| 06 | Context-Aware Escaping | One hostile reviewer, six contexts, six defences - and printing HTML you trust | Intermediate |
| 07 | Smart HTML Attributes | null drops an attribute, boolean flags, arrays in class/style, JSON in data- |
Intermediate |
| 08 | Including Templates | {include}, passing variables to a partial, and what a partial should own |
Intermediate |
| 09 | Layouts and Blocks | {layout}, {block} with defaults, {include parent} |
Intermediate |
| 10 | Definitions and Horizontal Reuse | {define} with parameters, {import}, and which reuse tool to pick |
Intermediate |
| 11 | Embedding Components | {embed}: a layout for a piece of a page, used three times on one page |
Intermediate |
| 12 | The Type System | Parameters as a class, {templateType}, {varType}, and why tools care |
Intermediate |
| 13 | Custom Filters and Functions | addFilter(), addFunction(), and which one a piece of logic should be |
Advanced |
| 14 | Your Own Extension and Tag | Latte\Extension, a custom {alert} tag, and the reference rule in node classes |
Advanced |
| 15 | Loaders | Templates from strings or a database, and how a loader drives the cache | Advanced |
| 16 | The Sandbox | Rendering templates you did not write, and the policy that keeps them polite | Expert |
| 17 | Development and Production | What to switch on where, the linter, and debugging with Tracy | Practical |
That is the whole course. If you work through it in order you will have met every part of Latte I use day to day.
Three things you will meet in the wild and will not find here, so you know they exist and where to look.
More tags and filters than fit in a course. Latte ships with dozens of tags and around fifty filters. You have met the ones that carry most templates; the rest are in the tag overview and the filter overview, both of which are single pages worth skimming once so you remember what is available. A few I use regularly and never found room for: {capture} to collect output into a variable, {spaceless}, {ifset}, {try} for a fragment that may fail, and {iterateWhile} for grouped listings.
Translations. TranslatorExtension adds {_'text'} and {translate}, and can translate static strings at compile time so each language gets its own compiled template. If your site is multilingual, start here.
Everything Latte gains inside Nette. Used with the framework, a whole layer of tags appears that this course never shows, because they come from other packages: n:href and {link} for URLs, {control} for components, {snippet} for AJAX, {form}, {label}, {input} and n:name for forms, {cache} for caching a fragment, {asset} for versioned assets. If you open a Nette project and meet n:href, it is not something you missed - those tags come from Nette itself, and the tag overview lists them with links to the package that documents each one.
Latte comes with a linter that catches syntax errors and references to tags or filters that do not exist. Point it at your own project, it is the cheapest CI step you will ever add:
composer lint-templatesThis repository also checks itself - composer check runs the linter, static analysis, and a script verifying that the output printed in each chapter still matches what the example produces.
- Latte documentation
- Why Latte is synonymous with safety - the chapter that converted me
- Nette forum
Happy templating!