Template Engines
By the end of this lesson you'll keep logic out of your HTML, escape output so it's XSS-safe, and use Twig's inheritance, includes, and filters to build clean, DRY views you'd ship to production.
Learn Python, JavaScript, Java and more with free interactive lessons, real projects and AI-powered help. Beginner-friendly.
Part of the free Php course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
What You'll Learn in This Lesson
1️⃣ The Problem: Logic Tangled With HTML
A template is the file that produces the HTML a visitor sees — the presentation layer. The trouble starts when you stuff business logic (deciding what to show, calculating values, talking to a database) into that same file. The result below works, but read it: HTML strings, an if decision, a loop, and string concatenation are all knotted together. Change the markup and you risk breaking the logic; change the logic and you risk breaking the markup.
The fix is separation of concerns : a controller prepares the data, and a template only displays it. That one rule is what every template engine — from plain PHP to Twig — exists to enforce.
2️⃣ Plain-PHP Templates (and Why You Must Escape)
You don't need a library to write a clean template. PHP's alternative syntax — if (...): ... endif; and foreach (...): ... endforeach; — plus the short echo tag ?= ... ? lets a template read almost like HTML. The non-negotiable rule: escape every value you print . htmlspecialchars() turns dangerous characters like into the harmless entity < , so a visitor can't smuggle a script into your page. Skipping this is the classic XSS (cross-site scripting) hole.
Notice Ada <dev> in the output: the dev was escaped, so it shows as text instead of becoming a (broken) HTML tag. That's the whole game — display user data, never execute it.
3️⃣ Capturing Output With a render() Function
So far a template prints straight to the browser. Real apps want the finished HTML as a string first — to wrap it in a layout, cache it, or send it in an email. That's what output buffering does: ob_start() begins capturing everything you echo, and ob_get_clean() stops and hands it back as a string. A six-line render() function built on this is the essence of every template engine.
4️⃣ Twig: Syntax, Filters & Auto-Escaping
Twig is the most popular PHP template engine (it ships with Symfony). It gives templates a tidy syntax of their own and — crucially — auto-escapes every output , so you get XSS protection for free. Three pieces of syntax cover most of it: {'{ value }'} prints a value (escaped), {' ... '} runs logic like if and for , and {' ... '} is a comment. Filters transform a value with a pipe — {'{ name|upper }'} — and chain left to right.
Look at the last line of output: the malicious script in review came out as escaped text , not a live tag — and you wrote no htmlspecialchars() at all. That automatic safety is the single biggest reason to use Twig over hand-rolled PHP. To print trusted HTML on purpose you'd opt out with the |raw filter — sparingly.
5️⃣ Template Inheritance & Partials
Repeating the header and footer in every page is the fastest way to an inconsistent site. Template inheritance fixes it: a base layout defines named {' block '} regions, and each page {' extends '} that base, overriding only the blocks it needs. For smaller reusable chunks — a nav bar, a product card — you {' include '} a partial . Together they keep your views DRY (Don't Repeat Yourself): one layout, many pages.
Here's the PHP that loads Twig and renders that template — note 'autoescape' => 'html' (XSS protection) and the cache directory, which compiles templates to plain PHP so rendering is fast in production.
6️⃣ Your Turn: Escape It Yourself
Time to practise. The plain-PHP template below prints data straight from a form, so it must escape. Fill in each ___ using the 👉 hint, then run it and check it against the Output panel.
Now a Twig one. Complete the template with the right filter and loop keyword, then study how Twig renders it.
📋 Quick Reference — Templating
No code is filled in this time — just a brief and an outline. Write the Twig template yourself, render it with the twig/twig library (or translate it to a plain-PHP template and run it on onecompiler.com/php ), then check it against the expected output. This is the write-run-check loop you'll use on every real view.
Practice quiz
What core principle does a template engine enforce?
- All code in one file
- Templates should run database queries
- Separation of concerns: controllers prepare data, templates only display it
- HTML must be generated with string concatenation
Answer: Separation of concerns: controllers prepare data, templates only display it. A controller prepares data and the template only presents it; that split is what every template engine exists to enforce.
In a plain-PHP template, which function escapes output to prevent XSS?
- htmlspecialchars($value, ENT_QUOTES, 'UTF-8')
- strip_tags()
- addslashes()
- nl2br()
Answer: htmlspecialchars($value, ENT_QUOTES, 'UTF-8'). htmlspecialchars turns < > " ' & into harmless entities so a visitor can't inject a working <script> tag.
What does htmlspecialchars do to the input '<script>'?
- Removes it entirely
- Executes it safely
- Leaves it unchanged
- Turns it into <script> so it displays as text
Answer: Turns it into <script> so it displays as text. The < and > become < and >, so the browser shows the text instead of running a tag.
Which pair of functions captures a template's echoed HTML as a string?
- fopen() / fclose()
- ob_start() / ob_get_clean()
- serialize() / unserialize()
- print() / sprintf()
Answer: ob_start() / ob_get_clean(). ob_start() begins capturing output and ob_get_clean() stops and returns it as a string - the heart of a render() function.
In Twig, what does {{ value }} do?
- Prints a value, auto-escaped by default
- Runs an if/for control structure
- Writes a comment
- Declares a variable without printing
Answer: Prints a value, auto-escaped by default. {{ }} prints a value and Twig auto-escapes it, so no manual htmlspecialchars is needed.
Which Twig delimiter is used for logic like if and for?
- {{ ... }}
- {# ... #}
- {% ... %}
- <?php ... ?>
Answer: {% ... %}. {% %} runs control flow (if, for, set); {{ }} prints values and {# #} is a comment.
Why is Twig's auto-escaping important?
- It makes templates render faster
- It converts dangerous characters automatically, preventing XSS without manual escaping
- It compresses the HTML
- It validates the HTML syntax
Answer: It converts dangerous characters automatically, preventing XSS without manual escaping. Auto-escaping every {{ }} turns a malicious <script> from user input into visible text, stopping XSS by default.
How do you intentionally print trusted raw HTML in Twig?
- Use the |upper filter
- Wrap it in {# #}
- It is impossible in Twig
- Use the |raw filter (sparingly)
Answer: Use the |raw filter (sparingly). The |raw filter opts out of auto-escaping; only use it for HTML you generated yourself, never on user input.
What is the difference between extends and include in Twig?
- They are identical
- extends is inheritance for the page shell; include composes in a reusable partial
- include is only for CSS files
- extends only works once per project
Answer: extends is inheritance for the page shell; include composes in a reusable partial. extends inherits a base layout and overrides its named blocks; include drops a reusable partial (nav, card) into the current spot.
How does Laravel's Blade compare to Twig?
- Blade does not auto-escape output
- Blade only works without a framework
- Same concepts, different syntax: @ directives and {{ }} that auto-escape, {!! !!} for raw HTML
- Blade cannot do template inheritance
Answer: Same concepts, different syntax: @ directives and {{ }} that auto-escape, {!! !!} for raw HTML. Blade mirrors Twig's ideas (inheritance, sections, includes, escaping) using @if/@foreach/@extends and {{ }} echoes that auto-escape.