PHP and HTML
PHP was created to generate HTML, and mixing the two is one of its most practical features. This lesson explains how PHP and HTML work together in the same file, how PHP decides what to send to the browser, and how to structure mixed files so they stay readable and secure.
How PHP Processes a Mixed File
When PHP runs a file, it reads it from top to bottom in one of two modes:
- Output mode — text is copied directly to the output. This is the default mode at the start of every file.
- PHP mode — text is executed as code. Entered with
<?phpor<?=, exited with?>.
The PHP manual calls switching out of PHP mode "escaping from HTML". Everything outside the PHP tags is treated as plain output, whether it is HTML, CSS, JavaScript, or ordinary text.
Example
<!DOCTYPE html>
<html>
<body>
<h1>Order Summary</h1>
<?php
$items = 3;
$total = 2450;
?>
<p>Items: <?= $items ?></p>
<p>Total: ₹<?= $total ?></p>
</body>
</html>
Output (HTML received by the browser)
<!DOCTYPE html>
<html>
<body>
<h1>Order Summary</h1>
<p>Items: 3</p>
<p>Total: ₹2450</p>
</body>
</html>
How It Works
- PHP starts in output mode, so the doctype,
<html>,<body>, and<h1>are copied unchanged. <?phpswitches into PHP mode. The two assignments run but output nothing.?>switches back. The single newline right after?>is removed, which is why the<p>line appears directly after the indentation spaces that preceded<?php.- Each
<?= ... ?>prints a value at the exact position where it appears. - The remaining HTML is copied unchanged.
The browser receives only HTML. It has no way to know which parts were written by hand and which were generated by PHP.
Note: The extra indentation in the output is harmless. Browsers ignore whitespace between elements when rendering. It only affects how the source looks in View Source.
Two Ways to Output HTML
There are two basic techniques for producing HTML from PHP.
Technique 1 — Leave PHP Mode and Write HTML Directly
<?php
$username = "Meera";
?>
<div class="welcome">
<h2>Welcome, <?= $username ?>!</h2>
<p>You have new messages.</p>
</div>
Technique 2 — Print HTML From Inside PHP
<?php
$username = "Meera";
echo '<div class="welcome">';
echo "<h2>Welcome, $username!</h2>";
echo '<p>You have new messages.</p>';
echo '</div>';
Output (both techniques)
<div class="welcome">
<h2>Welcome, Meera!</h2>
<p>You have new messages.</p>
</div>
(Technique 2 produces the same elements on a single line, because echo does not add line breaks.)
Comparing the Techniques
| Aspect | Leave PHP Mode | Echo HTML Strings |
|---|---|---|
| Readability for large markup | High — looks like normal HTML | Low — HTML is hidden inside strings |
| Editor support (highlighting, autocomplete, tag matching) | Full HTML support | Usually none inside strings |
| Quote conflicts | None | Frequent — HTML attributes use quotes too |
| Best for | Page templates, components, lists | Very small fragments, CLI output, building strings to return from functions |
Why Leaving PHP Mode Is Usually Better
Look at how quotes complicate Technique 2:
<?php
$url = "/profile";
echo "<a href=\"$url\" class=\"btn\">View profile</a>";
Because the string uses double quotes, every double quote in the HTML must be escaped with a backslash (\"). With more attributes, this becomes difficult to read and easy to break. The same link written in output mode has no escaping at all:
<a href="<?= $url ?>" class="btn">View profile</a>
For anything larger than a line or two of markup, write the HTML in output mode and insert values with <?= ?>.
Conditional HTML
PHP can decide whether a block of HTML is sent at all. When an if condition is false, PHP skips everything inside that block — including HTML written in output mode.
Example
<?php
$isLoggedIn = false;
$username = "Meera";
?>
<nav>
<a href="/">Home</a>
<?php if ($isLoggedIn) { ?>
<a href="/account">Hi, <?= $username ?></a>
<a href="/logout">Log out</a>
<?php } else { ?>
<a href="/login">Log in</a>
<a href="/register">Sign up</a>
<?php } ?>
</nav>
Output
<nav>
<a href="/">Home</a>
<a href="/login">Log in</a>
<a href="/register">Sign up</a>
</nav>
Why This Works
The curly braces of the if statement span across the PHP blocks. PHP compiles the whole file as one program, so the HTML between <?php if (...) { ?> and <?php } ?> becomes part of the if branch. When the condition is false, that output is never produced. This is much more efficient and readable than storing the HTML in strings and echoing them conditionally.
The Same Example With Alternative Syntax
Matching braces across several PHP blocks is hard to follow in long templates. PHP offers an alternative syntax designed for this situation:
<nav>
<a href="/">Home</a>
<?php if ($isLoggedIn): ?>
<a href="/account">Hi, <?= $username ?></a>
<a href="/logout">Log out</a>
<?php else: ?>
<a href="/login">Log in</a>
<a href="/register">Sign up</a>
<?php endif; ?>
</nav>
The : replaces the opening brace, and endif; replaces the closing brace. A lone } gives no hint about which block it closes; endif;, endforeach;, and endwhile; do. The Alternative Syntax lesson covers this in full.
Generating Repeated HTML With Loops
Loops generate HTML for every item in a list — table rows, cards, menu items, and options.
Example: Building a Table
<?php
$students = [
['name' => 'Arun', 'course' => 'PHP', 'score' => 88],
['name' => 'Divya', 'course' => 'JavaScript', 'score' => 92],
['name' => 'Karthik','course' => 'Python', 'score' => 76],
];
?>
<table>
<thead>
<tr>
<th>Name</th>
<th>Course</th>
<th>Score</th>
</tr>
</thead>
<tbody>
<?php foreach ($students as $student): ?>
<tr>
<td><?= $student['name'] ?></td>
<td><?= $student['course'] ?></td>
<td><?= $student['score'] ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
Output (body rows)
<tr>
<td>Arun</td>
<td>PHP</td>
<td>88</td>
</tr>
<tr>
<td>Divya</td>
<td>JavaScript</td>
<td>92</td>
</tr>
<tr>
<td>Karthik</td>
<td>Python</td>
<td>76</td>
</tr>
How It Works
$studentsis an array of associative arrays — each student is a set of named values. Arrays are covered in the PHP Arrays section.foreach ($students as $student):repeats the HTML between it andendforeach;once per student.- On each repetition,
$studentholds the current student, and$student['name']reads thenamevalue.
The table markup is written once, and PHP repeats it for each record. In a real application, $students would come from a database query, and the template would not change at all — whether there are three rows or three thousand.
Using PHP Values in HTML Attributes
PHP output can appear anywhere in the HTML, including inside attributes.
Example
<?php
$productId = 105;
$productName = "Laptop Stand";
$isFeatured = true;
$imageFile = "laptop-stand.jpg";
?>
<div class="product<?= $isFeatured ? ' product--featured' : '' ?>" data-id="<?= $productId ?>">
<img src="/images/<?= $imageFile ?>" alt="<?= $productName ?>">
<a href="/product.php?id=<?= $productId ?>">View details</a>
</div>
Output
<div class="product product--featured" data-id="105">
<img src="/images/laptop-stand.jpg" alt="Laptop Stand">
<a href="/product.php?id=105">View details</a>
</div>
How It Works
- The ternary
$isFeatured ? ' product--featured' : ''adds a CSS class only for featured products. The leading space in' product--featured'separates the two class names. data-id,src,alt, andhrefare filled with PHP values.
Attributes are a place where escaping matters most. If $productName contained a double quote, such as 24" Monitor, the unescaped output would be alt="24" Monitor" — the quote ends the attribute early and breaks the markup. The next section shows how to prevent this.
Escaping Output: Keeping Mixed HTML Safe
What the Problem Is
When a value is inserted into HTML, the browser interprets it as HTML. If the value contains characters such as <, >, ", or &, it can break the page — or, if the value came from a user, inject code into it.
Example of the Problem
<?php
// Imagine this comment came from a form submission
$comment = '<script>alert("Hacked")</script>';
?>
<p><?= $comment ?></p>
The browser receives a real <script> element and executes it. This attack is called cross-site scripting (XSS). A real attacker would not show an alert; they would steal session cookies or perform actions as the logged-in user.
The Fix: htmlspecialchars()
<p><?= htmlspecialchars($comment) ?></p>
Output
<p><script>alert("Hacked")</script></p>
Why This Works
htmlspecialchars() converts special characters into HTML entities:
| Character | Entity |
|---|---|
& | & |
< | < |
> | > |
" | " |
' | ' |
The browser displays entities as the original characters, so the visitor sees the comment text exactly as typed — but the browser no longer treats it as markup. Since PHP 8.1, htmlspecialchars() converts both double and single quotes by default, so values are safe inside attributes using either quote style.
A Helper Function for Templates
Because escaping is needed so often, many projects define a short helper:
<?php
function e(?string $value): string
{
return htmlspecialchars($value ?? '', ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}
?>
<img src="/images/<?= e($imageFile) ?>" alt="<?= e($productName) ?>">
?stringallowsnullto be passed;$value ?? ''turnsnullinto an empty string.ENT_QUOTESescapes both quote types;ENT_SUBSTITUTEreplaces invalid UTF-8 sequences instead of returning an empty string. Passing the flags explicitly keeps the behavior consistent regardless of PHP version.- Template engines such as Laravel's Blade and Twig escape output automatically for the same reason.
Warning: Escape every value that comes from outside your code — form input, URLs, cookies, databases, and APIs. Data stored in your own database can still contain malicious content that was submitted by a user earlier. The full topic is covered in Escaping Output (htmlspecialchars) and XSS Prevention.
Passing PHP Data to JavaScript
Sometimes JavaScript running in the browser needs data from PHP, such as configuration values or a list of items. The safe way to transfer data is json_encode().
Example
<?php
$settings = [
'currency' => 'INR',
'taxRate' => 0.18,
'userName' => "O'Brien </script>",
];
?>
<script>
const settings = <?= json_encode($settings, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT) ?>;
console.log(settings.currency, settings.taxRate, settings.userName);
</script>
Output (in the page source)
<script>
const settings = {"currency":"INR","taxRate":0.18,"userName":"O\u0027Brien \u003C\/script\u003E"};
console.log(settings.currency, settings.taxRate, settings.userName);
</script>
How It Works
json_encode()converts the PHP array into JSON, which is valid JavaScript object syntax.- The
JSON_HEX_*flags convert<,>,&,', and"into Unicode escape sequences such as\u003C. JavaScript decodes them back to the original characters, sosettings.userNameis stillO'Brien </script>. - Without these flags, the text
</script>inside the data would close the script element early in the HTML parser, breaking the page and opening an injection point.
Never build JavaScript values by concatenating raw PHP strings, such as const name = "<?= $name ?>";. A quote or line break in $name breaks the script, and malicious input can inject code.
Reusing HTML With Include Files
Most pages share a header, navigation, and footer. Instead of copying that HTML into every page, place it in separate files and include it.
Project Structure
site/
├── includes/
│ ├── header.php
│ └── footer.php
├── index.php
└── about.php
includes/header.php
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title><?= htmlspecialchars($pageTitle) ?> | My Site</title>
<link rel="stylesheet" href="/css/style.css">
</head>
<body>
<header>
<a href="/">Home</a>
<a href="/about.php">About</a>
</header>
<main>
includes/footer.php
</main>
<footer>
<p>© <?= date('Y') ?> My Site</p>
</footer>
</body>
</html>
about.php
<?php
$pageTitle = "About Us";
require __DIR__ . '/includes/header.php';
?>
<h1>About Us</h1>
<p>We build tutorials for developers.</p>
<?php require __DIR__ . '/includes/footer.php'; ?>
How It Works
about.phpsets$pageTitlebefore including the header.requireinserts and runsheader.phpat that point. Included files share the variables of the code that includes them, so the header can use$pageTitle.- The page's own content is output.
footer.phpcloses the layout and prints the current year withdate('Y').__DIR__builds paths relative to the current file, so the includes work no matter which directory the script is started from.
Changing the navigation now means editing one file instead of every page. include and require are covered in detail in PHP Include and Include vs Require.
Separating Logic From Presentation
As pages grow, mixing database queries, calculations, and HTML in one place becomes hard to maintain. A simple and effective rule is: prepare all data first, then output it.
Hard to Maintain
<ul>
<?php
$products = getProducts();
foreach ($products as $product) {
if ($product['stock'] > 0) {
$price = $product['price'] * 1.18;
echo '<li>' . $product['name'] . ' - ' . round($price, 2) . '</li>';
}
}
?>
</ul>
Easier to Maintain
<?php
// 1. Logic: get and prepare data
$products = getProducts();
$available = array_filter($products, fn($p) => $p['stock'] > 0);
foreach ($available as &$product) {
$product['displayPrice'] = number_format($product['price'] * 1.18, 2);
}
unset($product);
?>
<!-- 2. Presentation: only output -->
<ul>
<?php foreach ($available as $product): ?>
<li><?= htmlspecialchars($product['name']) ?> - ₹<?= $product['displayPrice'] ?></li>
<?php endforeach; ?>
</ul>
Why the Second Version Is Better
- The template part contains only loops, simple conditions, and escaped output, so a designer can edit it without understanding the business logic.
- The logic can later be moved into a function or class without touching the HTML.
- Escaping happens in one obvious place.
Some functions in this example (array_filter, arrow functions fn, references with &) are covered in later lessons. The structure is the point: logic on top, presentation below. Frameworks formalize this pattern with controllers and view templates, often called MVC (Model–View–Controller).
Common Mistakes
Forgetting to Close PHP Mode Before HTML
<?php
$title = "Home";
<h1><?= $title ?></h1>
Result: Parse error: syntax error, unexpected token "<".
Fix: Add ?> before the HTML.
Writing PHP Tags Inside an echo String
<?php
echo "<p>Hello, <?= $name ?></p>";
Result: The <?= inside a string is just text. Because the string uses double quotes, $name is interpolated, so the browser receives something like <p>Hello, <?= Meera ?></p>, and the stray <?= appears in the source.
Fix: Inside PHP code, join values directly: echo "<p>Hello, $name</p>";
Not Escaping Output
Problem: Values from users or databases are printed directly with <?= $value ?>.
Effect: Broken markup when values contain quotes or <, and XSS vulnerabilities.
Fix: Use htmlspecialchars() (or an e() helper) for every external value.
Mismatched Braces Across PHP Blocks
Problem: A { opened in one PHP block is never closed, or closed in the wrong place.
Effect: Parse error: syntax error, unexpected end of file, often reported at the last line of the file.
Fix: Use the alternative syntax (if: ... endif;, foreach: ... endforeach;) in templates, which makes each block's end explicit.
Opening the File Without a Server
Problem: The .php file is opened directly in the browser using a file:// URL.
Effect: The PHP code is shown or downloaded instead of executed.
Fix: Serve the file through PHP, for example with php -S localhost:8000.
Best Practices
- Write large HTML sections in output mode and insert values with
<?= ?>. - Use the alternative control-structure syntax in templates.
- Escape every external value with
htmlspecialchars(). - Transfer data to JavaScript with
json_encode()and theJSON_HEX_*flags. - Put shared layout parts (header, footer, navigation) in include files.
- Prepare data before the HTML starts; keep templates free of database queries and complex calculations.
- Set
<meta charset="UTF-8">and save files as UTF-8 so that PHP output and the browser's interpretation use the same encoding.
Official reference: https://www.php.net/manual/en/language.basic-syntax.phpmode.php