web php

Templates

Templates are files that turn application data into HTML. They keep page markup away from request handling, database calls, and business rules.

A beginner PHP application often starts by mixing everything in one file: read $_POST, query data, decide permissions, and echo HTML. Templates are the next step toward code that is easier to read and change.

A simple render function

Plain PHP can be a template language. A view file can receive an array of data and output HTML.

This example uses output buffering to keep the render result as a string:

PHP example
<?php

declare(strict_types=1);

function escapeHtml(string $value): string
{
    return htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}

function renderUserCard(string $name, string $role): string
{
    ob_start();
    ?>
    <article>
        <h2><?= escapeHtml($name) ?></h2>
        <p><?= escapeHtml($role) ?></p>
    </article>
    <?php
    return trim((string) ob_get_clean());
}

echo renderUserCard('<Admin>', 'Support & billing') . PHP_EOL;

// Prints:
// <article>
//         <h2>&lt;Admin&gt;</h2>
//         <p>Support &amp; billing</p>
//     </article>

The important detail is not the exact whitespace. The important detail is that the template receives prepared data and escapes it at output time.

Keep request work outside the template

A template should not decide how to read input, load database rows, or send redirects. It should render data it was given.

Controller or page script responsibilities:

  • read request values
  • validate and authorize
  • call application services or database code
  • choose which template to render
  • pass simple view data

Template responsibilities:

  • show values
  • loop over prepared lists
  • branch for display states
  • escape output
  • include layout or partial files
PHP example
<?php

declare(strict_types=1);

$viewData = [
    'title' => 'Invoices',
    'rows' => [
        ['number' => 'INV-1001', 'total' => '99.00'],
        ['number' => 'INV-1002', 'total' => '149.00'],
    ],
];

echo $viewData['title'] . ': ' . count($viewData['rows']) . ' rows' . PHP_EOL;

// Prints:
// Invoices: 2 rows

Preparing view data before rendering makes the template boring. Boring templates are easier to review.

Layouts and partials

A layout is the shared page frame: doctype, <html>, header, navigation, main area, and footer.

A partial is a smaller reusable template, such as a navigation item, error summary, flash message, or invoice row.

Use partials to remove real duplication, not to hide every three lines of HTML. Too many tiny partials can make pages harder to follow.

PHP example
<?php

declare(strict_types=1);

function renderLayout(string $title, string $content): string
{
    return '<!doctype html><title>'
        . htmlspecialchars($title, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8')
        . '</title><main>'
        . $content
        . '</main>';
}

echo renderLayout('Dashboard', '<h1>Welcome</h1>') . PHP_EOL;

// Prints:
// <!doctype html><title>Dashboard</title><main><h1>Welcome</h1></main>

The content passed into a layout should already be trusted template output. Do not pass raw user input as layout content.

Avoid heavy logic in templates

Small display branches are fine:

PHP example
<?php if ($invoices === []): ?>
    <p>No invoices yet.</p>
<?php else: ?>
    <!-- render invoice rows -->
<?php endif; ?>

Complex permission checks, database queries, payment calculations, and request parsing should happen before the template. When templates contain too much logic, they become hard to test and easy to break.

What to check in a project

Check that templates escape values when rendering HTML.

Check that request globals such as $_GET, $_POST, and $_SESSION are not scattered through templates. Some framework helpers are normal, but templates should not become controllers.

Check that data passed to templates has clear names. $userDisplayName is easier to review than $x.

Check raw HTML output carefully. It should be rare and justified, such as rendering sanitized rich text.

What you should be able to do

After this lesson, you should be able to explain why templates exist, pass prepared data into a view, escape output, use layouts and partials sensibly, and keep business logic out of templates.

Practice

Task: Render A Small Template

Write a small PHP script that renders prepared user data into HTML.

Requirements

  • Use declare(strict_types=1);.
  • Create an escapeHtml() helper.
  • Create a render function that accepts a title and a list of rows.
  • Escape every value printed into HTML.
  • Include one normal case with two rows.
  • Include one edge case with no rows.
  • Print both rendered outputs.

Check Your Work

Run the script and confirm that the empty state appears and that special characters are escaped.

Show solution

This solution keeps data preparation separate from rendering. The template function only receives the values it needs.

PHP example
<?php

declare(strict_types=1);

function escapeHtml(string $value): string
{
    return htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}

/**
 * @param list<array{name: string, role: string}> $rows
 */
function renderUsers(string $title, array $rows): string
{
    ob_start();
    ?>
    <section>
        <h1><?= escapeHtml($title) ?></h1>
        <?php if ($rows === []): ?>
            <p>No users found.</p>
        <?php else: ?>
            <ul>
                <?php foreach ($rows as $row): ?>
                    <li><?= escapeHtml($row['name']) ?>: <?= escapeHtml($row['role']) ?></li>
                <?php endforeach; ?>
            </ul>
        <?php endif; ?>
    </section>
    <?php

    return trim((string) ob_get_clean());
}

$normal = renderUsers('Team', [
    ['name' => 'Asha', 'role' => 'Developer'],
    ['name' => '<Kai>', 'role' => 'Support & billing'],
]);

$empty = renderUsers('Team', []);

echo $normal . PHP_EOL;
echo $empty . PHP_EOL;

// Prints:
// <h1>Team</h1>
// <li>&lt;Kai&gt;: Support &amp; billing</li>
// <p>No users found.</p>

In a real application, the rows would usually be prepared by a controller or page script before the template renders.

Why This Works

The normal case proves the template can render a list. The empty case proves display logic stays simple. The <Kai> row proves output is escaped at the point where HTML is produced.