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 &lt;script&gt; so it displays as text

Answer: Turns it into &lt;script&gt; so it displays as text. The < and > become &lt; and &gt;, 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.