PHP is a template language
A .php file can contain HTML with small islands of PHP. Everything outside the PHP tags is sent to the browser unchanged; everything inside runs on the server. You can switch in and out as often as you like, which makes PHP a natural language for building pages.
This lesson builds a small product listing. The data would normally come from a database; here it is an array at the top of the file, so you can concentrate on the template.
The short echo tag
Printing values is the most common thing a template does, so PHP has a short form for it. <?= $value ?> means exactly the same as <?php echo $value; ?>, and it is always enabled:
<?php $name = "Asha"; ?>
<p>Hello, <?= $name ?>!</p>
<p>Today is <?= date('l', mktime(0, 0, 0, 6, 3, 2024)) ?>.</p>GET /hello.php<p>Hello, Asha!</p>
<p>Today is Monday.</p>Note that the first line, the PHP block that sets $name, produced no output, and PHP also swallowed the single line break directly after its closing ?>. That is why the output starts with the paragraph.
Loops and conditions in templates
Curly braces get lost between HTML tags. The alternative syntax replaces the opening brace with a colon and the closing brace with endif;, endforeach;, endfor; or endwhile;, so each block is labelled:
<?php
$products = [
['name' => 'Mechanical Keyboard', 'price' => 89.5, 'stock' => 60],
['name' => 'Puzzle 1000 Pieces', 'price' => 19.99, 'stock' => 0],
['name' => 'Ceramic Mug Set', 'price' => 18.75, 'stock' => 4],
];
?>
<h1>Products</h1>
<?php if (count($products) === 0): ?>
<p>No products yet.</p>
<?php else: ?>
<ul>
<?php foreach ($products as $product): ?>
<li>
<?= $product['name'] ?>: ₹<?= number_format($product['price'], 2) ?>
<?php if ($product['stock'] === 0): ?>
<strong>Sold out</strong>
<?php elseif ($product['stock'] < 10): ?>
<em>Only <?= $product['stock'] ?> left</em>
<?php endif; ?>
</li>
<?php endforeach; ?>
</ul>
<?php endif; ?>GET /products.php<h1>Products</h1>
<ul>
<li>
Mechanical Keyboard: ₹89.50 </li>
<li>
Puzzle 1000 Pieces: ₹19.99 <strong>Sold out</strong>
</li>
<li>
Ceramic Mug Set: ₹18.75 <em>Only 4 left</em>
</li>
</ul>The indentation of the output looks untidy, because the whitespace around the PHP tags is kept. Browsers ignore it, so it does not matter for the page; it is only a little surprising in View Source. What matters is that the structure of the template is easy to read.
Escaping output with htmlspecialchars
Now suppose a product name comes from a form or a database, where anyone could have typed anything. If the text contains HTML, printing it with <?= inserts that HTML into your page:
<?php
$review = 'Great mug! <script>alert("hacked")</script>';
?>
<p>Unsafe: <?= $review ?></p>
<p>Safe: <?= htmlspecialchars($review) ?></p>GET /review.php<p>Unsafe: Great mug! <script>alert("hacked")</script></p>
<p>Safe: Great mug! <script>alert("hacked")</script></p>In the unsafe line, the browser would run that script. A real attacker would not show an alert; they would steal the session cookies of everyone who views the page. This attack is called cross-site scripting (XSS) and it is one of the most common web vulnerabilities. In the safe line, htmlspecialchars() converted <, >, &, " and ' into HTML entities, so the browser shows the text exactly as it was typed and runs nothing.
The rule is simple: escape every value when you print it into HTML, whether it came from a user, a database or an API. Values you wrote yourself in the code, such as a fixed heading, do not need it, but escaping them does no harm. Escaping also keeps ordinary text correct: a product called "Salt & Pepper" should be printed as Salt & Pepper.
Typing htmlspecialchars() everywhere is tedious, so most projects define a tiny helper:
<?php
function e(?string $value): string
{
return htmlspecialchars($value ?? '', ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}<?php
require __DIR__ . '/helpers.php';
$product = ['name' => 'Salt & Pepper "Classic" Set', 'url' => '/p/salt-pepper'];
?>
<a href="<?= e($product['url']) ?>" title="<?= e($product['name']) ?>"><?= e($product['name']) ?></a>GET /card.php<a href="/p/salt-pepper" title="Salt & Pepper "Classic" Set">Salt & Pepper "Classic" Set</a>ENT_QUOTES (the default in PHP 8) escapes both kinds of quote, which matters inside attributes. Template engines such as Laravel's Blade and Twig escape automatically, which is one of the main reasons people use them. The web security lesson covers the cases that escaping alone does not, such as URLs in href.
Keep logic out of the template
Templates become hard to maintain when they also fetch data, validate forms and calculate prices. A common structure is:
- PHP at the top of the file (or in a separate file) prepares all the data.
- The HTML below only loops, checks simple conditions and prints escaped values.
Frameworks take this further with separate controller and view files, but the habit starts here.
Common mistakes
- Printing data without htmlspecialchars(), which opens the door to XSS.
- Mismatched alternative syntax, such as an
endif;missing inside a loop. PHP reports "syntax error, unexpected end of file". - Mixing braces and the alternative syntax in the same block.
- Doing database work in the middle of the HTML, which makes the template hard to read and test.
Exercise
Create menu.php with an array of three dishes, each with a name and a price, where one name contains an ampersand ("Rajma & Rice"). Print them as an HTML table with a header row, escape every name, and format prices with two decimals.
Show one possible answer
<?php
$dishes = [
['name' => 'Masala Dosa', 'price' => 120],
['name' => 'Rajma & Rice', 'price' => 150],
['name' => 'Filter Coffee', 'price' => 40],
];
?>
<table>
<tr><th>Dish</th><th>Price</th></tr>
<?php foreach ($dishes as $dish): ?>
<tr><td><?= htmlspecialchars($dish['name']) ?></td><td><?= number_format($dish['price'], 2) ?></td></tr>
<?php endforeach; ?>
</table>GET /menu.php<table>
<tr><th>Dish</th><th>Price</th></tr>
<tr><td>Masala Dosa</td><td>120.00</td></tr>
<tr><td>Rajma & Rice</td><td>150.00</td></tr>
<tr><td>Filter Coffee</td><td>40.00</td></tr>
</table>Key points
- Outside <?php ... ?> tags, everything in a .php file is sent as it is. Switch in and out of PHP as needed.
- <?= $value ?> is short for <?php echo $value; ?> and is always available.
- Use the alternative syntax (if: ... endif; foreach: ... endforeach;) in templates for readability.
- Escape every value you print into HTML with htmlspecialchars(), or a visitor's text can become code on your page.