π§© Embedding PHP in HTML
PHP was born to mix with HTML. Unlike languages that bolt on a separate template system, PHP lets you drop dynamic values, conditions, and loops straight into your markup β the original reason it took over the web. This lesson shows how to do it cleanly, and, just as importantly, safely.
π― Learning Objectives
By the end of this lesson, you will be able to:
- Switch between HTML and PHP using tags and the short echo (
<?= ?>) - Output dynamic content and escape it to prevent XSS
- Render markup conditionally and repeat it with loops using alternative syntax
- Split pages into reusable includes (header, footer, components)
- Apply core security rules for user-facing PHP pages
Estimated Time: 35β45 minutes β’ Difficulty: BeginnerβIntermediate
Hands-on: Build a small filterable product list from an array of data.
In This Lesson
How PHP and HTML Combine
A .php file is really an HTML file with "escape hatches". The server sends everything outside PHP tags to the browser untouched, and executes everything inside them, splicing the results into the output stream.
π‘ Analogy: HTML is the fixed dining room and printed menu; PHP is the chef who fills in today's specials. Diners (browsers) only ever see the finished room β never the kitchen.
This design lets you build data-driven pages β a product grid, a personalised dashboard, a comment thread β without leaving your markup.
Outputting & Escaping Content
Printing a value is easy. Printing it safely is the part that matters. Any data that came from a user or a database must be escaped before it lands in HTML, or an attacker can inject their own markup and scripts β a Cross-Site Scripting (XSS) attack.
β οΈ The golden rule
Escape on output, every time, with htmlspecialchars(). It converts <, >, &, and quotes into harmless HTML entities so the browser displays them as text instead of running them.
<!-- Unsafe: a comment containing <script> would execute -->
<p><?= $comment['text'] ?></p>
<!-- Safe: entities are shown literally -->
<p><?= htmlspecialchars($comment['text'], ENT_QUOTES, 'UTF-8') ?></p>
Typing htmlspecialchars() everywhere gets tedious, so define a tiny helper and use it throughout your templates:
<?php
function e(?string $text): string {
return htmlspecialchars($text ?? '', ENT_QUOTES, 'UTF-8');
}
?>
<p><?= e($comment['text']) ?></p>
Formatting for display
PHP has built-in functions for common display formatting:
<!-- Currency -->
<p class="price">$<?= number_format($product['price'], 2) ?></p>
<!-- Dates -->
<time><?= date("F j, Y", strtotime($article['published'])) ?></time>
Conditional Markup
You can render entirely different HTML depending on your data. In templates, PHP's alternative syntax (if: β¦ endif;) reads far better than curly braces buried in markup.
<?php if ($isLoggedIn): ?>
<div class="dashboard">
<h2>Welcome back, <?= e($username) ?></h2>
<a href="/logout.php">Log out</a>
</div>
<?php else: ?>
<div class="login-prompt">
<p>Please <a href="/login.php">log in</a> to continue.</p>
</div>
<?php endif; ?>
Multiple branches and conditional classes
<div class="user-card">
<?php if ($user['role'] === 'admin'): ?>
<span class="badge admin">Administrator</span>
<?php elseif ($user['role'] === 'moderator'): ?>
<span class="badge moderator">Moderator</span>
<?php else: ?>
<span class="badge member">Member</span>
<?php endif; ?>
</div>
<!-- Toggle a CSS class inline with a ternary -->
<div class="product <?= $product['in_stock'] ? '' : 'out-of-stock' ?>">
<?= e($product['name']) ?>
</div>
π Alternative syntax cheat sheet
Every block construct has an end... form for templates: if:/endif;, foreach:/endforeach;, for:/endfor;, and while:/endwhile;.
Loops in Templates
Generating repetitive HTML β cards, rows, list items β is one of PHP's most common jobs. A foreach over an array is the workhorse pattern:
<div class="product-grid">
<?php foreach ($products as $product): ?>
<div class="product-card">
<h3><?= e($product['name']) ?></h3>
<p class="price">$<?= number_format($product['price'], 2) ?></p>
<button data-id="<?= (int) $product['id'] ?>">Add to Cart</button>
</div>
<?php endforeach; ?>
</div>
Handle the empty case
Always account for "no data" β a blank grid confuses users. Combine a condition with the loop:
<table class="data-table">
<thead>
<tr><th>Name</th><th>Email</th></tr>
</thead>
<tbody>
<?php if (count($users) > 0): ?>
<?php foreach ($users as $user): ?>
<tr>
<td><?= e($user['name']) ?></td>
<td><?= e($user['email']) ?></td>
</tr>
<?php endforeach; ?>
<?php else: ?>
<tr><td colspan="2" class="no-data">No users found.</td></tr>
<?php endif; ?>
</tbody>
</table>
Reusable Includes
Copy-pasting a header into every page is a maintenance nightmare. PHP's include and require pull one file into another at runtime β server-rendered components, years before frontend frameworks made the idea popular.
| Statement | On failure | Typical use |
|---|---|---|
include | Warning, keeps running | Optional pieces |
require | Fatal error, stops | Essential files (config) |
include_once | Warning; won't re-include | Templates that might repeat |
require_once | Fatal; won't re-include | Class & library definitions |
<!-- index.php -->
<?php
$pageTitle = "Home";
require __DIR__ . '/includes/header.php'; // opens <html>β¦<body>
?>
<main>
<h1><?= e($pageTitle) ?></h1>
<p>Welcome to the site.</p>
</main>
<?php require __DIR__ . '/includes/footer.php'; ?>
π‘ Use __DIR__ for reliable paths
Prefixing include paths with __DIR__ (the current file's directory) makes them work no matter which script did the including, avoiding "file not found" surprises. Included files share the variable scope of wherever they're included, so $pageTitle is visible inside header.php.
Security Essentials
Mixing PHP with HTML puts user data next to executable markup, so a few rules are non-negotiable.
β οΈ The three rules
- Escape on output. Wrap every dynamic value in
htmlspecialchars()/ youre()helper. - Validate and sanitise input. Never trust
$_GET,$_POST, or$_COOKIE. - Use prepared statements. Never build SQL by concatenating user input.
Reading and checking input with the filter functions:
<?php
$errors = [];
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$email = filter_input(INPUT_POST, 'email', FILTER_SANITIZE_EMAIL);
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
$errors['email'] = 'Please enter a valid email.';
}
}
Querying a database safely with PDO prepared statements β user input is sent separately from the query, so it can never alter its structure:
<?php
$categoryId = (int) ($_GET['category'] ?? 0);
$stmt = $db->prepare('SELECT * FROM products WHERE category_id = ?');
$stmt->execute([$categoryId]);
$products = $stmt->fetchAll(PDO::FETCH_ASSOC);
?>
<?php foreach ($products as $product): ?>
<h3><?= e($product['name']) ?></h3>
<?php endforeach; ?>
π CSRF, in one line
Forms that change data should carry a secret CSRF token (a random value stored in the session and echoed into a hidden field) that you verify on submission. This stops other sites from tricking a logged-in user's browser into submitting your form. You'll go deeper on this in the security modules.
Hands-on Exercise
ποΈ A Filterable Product List
Objective: Combine top-of-file logic, escaping, a query-string filter, and a template loop.
Instructions:
- Define an array of products, each with
name,price, andcategory. - Read an optional
?category=value from the URL with??. - Filter the array to the chosen category (or show all).
- Loop over the result with
foreach/endforeach, escaping names and formatting prices. Show a friendly message when nothing matches.
π‘ Hint
Use array_filter() with an arrow function that captures the filter via use (or automatically, for arrow functions). Guard the output with if (count($filtered) > 0): ... else: ... endif;. Cast the price with number_format($p, 2).
β Example solution
<?php
function e(?string $t): string {
return htmlspecialchars($t ?? '', ENT_QUOTES, 'UTF-8');
}
$products = [
['name' => 'Keyboard', 'price' => 79.50, 'category' => 'input'],
['name' => 'Mouse', 'price' => 29.00, 'category' => 'input'],
['name' => 'Monitor', 'price' => 199.0, 'category' => 'display'],
];
$filter = $_GET['category'] ?? null;
$filtered = $filter
? array_filter($products, fn($p) => $p['category'] === $filter)
: $products;
?>
<h1>Products<?= $filter ? ": " . e($filter) : "" ?></h1>
<div class="grid">
<?php if (count($filtered) > 0): ?>
<?php foreach ($filtered as $product): ?>
<div class="card">
<h3><?= e($product['name']) ?></h3>
<p>$<?= number_format($product['price'], 2) ?></p>
</div>
<?php endforeach; ?>
<?php else: ?>
<p>No products in that category.</p>
<?php endif; ?>
</div>
π― Quick Quiz
Question 1: What is <?= $name ?> shorthand for?
Question 2: Why must you wrap user content in htmlspecialchars() before output?
Question 3: Which include statement causes a fatal error and stops the script if the file is missing?
Summary & Quiz
π Key Takeaways
- A
.phpfile is HTML with PHP escape hatches; the short echo<?= ?>keeps templates tidy. - Do logic at the top, presentation below β and escape every dynamic value with
htmlspecialchars(). - Alternative syntax (
if:/endif;,foreach:/endforeach;) makes conditional and repeated markup readable. include/requirebuild reusable headers, footers, and components; use__DIR__for paths.- Security rules: escape output, validate input, use prepared statements, protect forms with CSRF tokens.
π Further Reading
- PHP Manual β Escaping from HTML
- PHP Manual β Alternative Syntax
- OWASP β Cross-Site Scripting
- PHP: The Right Way β Templating
π What's Next?
You can now generate dynamic pages end to end. Next we shift from single languages to cross-language patterns, starting with Data Structures Across Languages β comparing arrays, objects, and collections in Node, Python, and PHP.
π You built a dynamic page!
Mixing PHP and HTML safely is the everyday craft of server-side web development. From here, patterns just get more powerful.