practical capstone projects
Template Rendering
Templates turn application data into HTML. Add product templates to the catalog app while keeping repository calls, validation, and authorization outside the view files.
Render A Known Template
Create a renderer that receives a code-owned template path and an explicit context:
<?php
declare(strict_types=1);
function render(string $template, array $context): string
{
extract($context, EXTR_SKIP);
ob_start();
require dirname(__DIR__) . '/templates/' . $template . '.php';
return (string) ob_get_clean();
}
function e(string $value): string
{
return htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}
Only application code chooses $template. Never pass request text into a dynamic include path.
Prepare Data Before Rendering
A controller can load rows and prepare a narrow context:
<?php
echo render('products/index', [
'products' => $repository->list(limit: 50),
]);
Inside templates/products/index.php, escape product names with e((string) $product['name']). Format trusted integer prices deliberately. Do not run SQL or permission checks in the template.
Verify Escaping
Create a product named <script>alert(1)</script> and load the list page. The page should display the characters as text; it must not execute markup. Check text nodes and HTML attributes separately because each output context has its own escaping rules.
Practice
Practice: Render A Safe Product Card
Implement the renderer and escaped product list template from the lesson.
Requirements
- Use a narrow template context array.
- Escape HTML text and attributes with correct context-aware helpers.
- Keep database access and business rules outside templates.
- Separate trusted markup from ordinary text.
- Do not rely on input sanitisation instead of output escaping.
- Avoid global variables hidden inside templates.
- Do not execute template filenames chosen by request data.
Insert a product name containing HTML markup and prove the page displays text rather than executing markup.
Show solution
<?php foreach ($products as $product): ?>
<article>
<h2><?= e((string) $product['name']) ?></h2>
<p><?= (int) $product['price_cents'] ?> cents</p>
</article>
<?php endforeach; ?>
The repository call stays in the controller. The template receives prepared data and escapes untrusted text where it becomes HTML.