Templates
Templates
Minimal Boot uses a native PHP template system that provides excellent performance, debugging capabilities, and full access to PHP features without the overhead of template compilation.
Template System Overview
Why Native PHP Templates?
- No Compilation - Templates are executed directly as PHP
- Better Performance - No template compilation overhead
- Full PHP Power - Access to all PHP functions and features
- Easy Debugging - Standard PHP debugging tools work seamlessly
- No Learning Curve - If you know PHP, you know the template syntax
Template Architecture
Templates/
├── Layout System # Hierarchical template inheritance
├── Helper Functions # URL generation, escaping, utilities
├── Component Support # Reusable template components
└── Security Features # XSS protection, CSRF tokens
Basic Template Usage
Simple Template
Create src/Module/templates/example.phtml
:
<?php
// Set layout and pass data to it
$layout('layout::default', [
'title' => 'Example Page',
'description' => 'This is an example page',
]);
?>
<div class="container">
<h1><?= $this->escapeHtml($title) ?></h1>
<p><?= $this->escapeHtml($content) ?></p>
</div>
Template Variables
Variables are passed from handlers:
// In handler
return new HtmlResponse(
$this->template->render('module::template', [
'title' => 'Page Title',
'content' => 'Page content',
'items' => ['item1', 'item2', 'item3'],
])
);
<!-- In template -->
<h1><?= $this->escapeHtml($title) ?></h1>
<p><?= $this->escapeHtml($content) ?></p>
<ul>
<?php foreach ($items as $item): ?>
<li><?= $this->escapeHtml($item) ?></li>
<?php endforeach; ?>
</ul>
Layout System
Using Layouts
Layouts provide consistent structure across pages:
<?php
$layout('layout::default', [
'title' => 'Page Title',
'description' => 'SEO description',
'author' => 'Author Name',
'keywords' => 'keyword1, keyword2',
]);
?>
<!-- Page content goes here -->
<main class="content">
<h1>Welcome</h1>
</main>
Available Layouts
Default Layout (layout::default
)
Bootstrap-based layout with navigation:
$layout('layout::default', [
'title' => 'Page Title',
'description' => 'SEO description',
'author' => 'Author Name',
]);
Features:
- Bootstrap 5 CSS framework
- Responsive navigation
- Dark mode toggle
- SEO meta tags
Tailwind Layout (layout::tailwind
)
TailwindCSS-based layout with Alpine.js:
$layout('layout::tailwind', [
'title' => 'Page Title',
'description' => 'SEO description',
'cssUrl' => '/themes/main/assets/main.css',
'jsUrl' => '/themes/main/assets/main.js',
]);
Features:
- TailwindCSS utility classes
- Alpine.js for interactivity
- Modern design components
- Responsive layout
Bootstrap Layout (layout::bootstrap
)
Pure Bootstrap layout:
$layout('layout::bootstrap', [
'title' => 'Page Title',
'description' => 'SEO description',
]);
Features:
- Clean Bootstrap design
- Minimal JavaScript
- Fast loading
- Classic styling
Creating Custom Layouts
Create src/Shared/templates/layout/custom.phtml
:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title><?= $this->escapeHtml($title ?? 'Default Title') ?></title>
<meta name="description" content="<?= $this->escapeHtmlAttr($description ?? '') ?>">
<!-- Custom CSS -->
<link href="/css/custom.css" rel="stylesheet">
</head>
<body>
<header>
<nav class="navbar">
<!-- Navigation -->
</nav>
</header>
<main>
<?= $content ?>
</main>
<footer>
<!-- Footer content -->
</footer>
<!-- Custom JavaScript -->
<script src="/js/custom.js"></script>
</body>
</html>
Use the custom layout:
$layout('layout::custom', [
'title' => 'Custom Page',
'description' => 'Page with custom layout',
]);
Template Helpers
Escaping Functions
Always escape output to prevent XSS:
<!-- Escape HTML content -->
<h1><?= $this->escapeHtml($title) ?></h1>
<!-- Escape HTML attributes -->
<a href="<?= $this->escapeHtmlAttr($url) ?>"><?= $this->escapeHtml($linkText) ?></a>
<!-- Escape JavaScript -->
<script>
var data = <?= $this->escapeJs($jsonData) ?>;
</script>
<!-- Escape CSS -->
<style>
.class { color: <?= $this->escapeCss($color) ?>; }
</style>
<!-- Escape URLs -->
<a href="<?= $this->escapeUrl($dynamicUrl) ?>">Link</a>
URL Generation
Generate URLs using the URL helper:
<!-- Generate route URLs -->
<a href="<?= $url('page::index') ?>">Home</a>
<a href="<?= $url('page::view', ['slug' => 'about']) ?>">About</a>
<!-- Generate URLs with query parameters -->
<a href="<?= $url('blog::index', [], ['page' => 2]) ?>">Page 2</a>
<!-- Generate absolute URLs -->
<a href="<?= $url('page::index', [], [], null, ['force_canonical' => true]) ?>">Absolute URL</a>
Path Helpers
Access application paths:
<!-- Asset URLs -->
<img src="<?= $this->asset('/images/logo.png') ?>" alt="Logo">
<link href="<?= $this->asset('/css/style.css') ?>" rel="stylesheet">
<!-- Public path -->
<form action="<?= $this->path('/upload') ?>" method="post">
<!-- Form content -->
</form>
Template Components
Partial Templates
Create reusable template components:
Create src/Shared/templates/partial/pagination.phtml
:
<?php if ($totalPages > 1): ?>
<nav aria-label="Pagination">
<ul class="pagination">
<?php if ($currentPage > 1): ?>
<li class="page-item">
<a class="page-link" href="<?= $url($route, $routeParams, ['page' => $currentPage - 1]) ?>">Previous</a>
</li>
<?php endif; ?>
<?php for ($i = 1; $i <= $totalPages; $i++): ?>
<li class="page-item <?= $i === $currentPage ? 'active' : '' ?>">
<a class="page-link" href="<?= $url($route, $routeParams, ['page' => $i]) ?>"><?= $i ?></a>
</li>
<?php endfor; ?>
<?php if ($currentPage < $totalPages): ?>
<li class="page-item">
<a class="page-link" href="<?= $url($route, $routeParams, ['page' => $currentPage + 1]) ?>">Next</a>
</li>
<?php endif; ?>
</ul>
</nav>
<?php endif; ?>
Use the partial:
<!-- In your template -->
<?= $this->render('partial::pagination', [
'currentPage' => $currentPage,
'totalPages' => $totalPages,
'route' => 'blog::index',
'routeParams' => [],
]) ?>
Template Inheritance
Create base templates and extend them:
Create src/Shared/templates/base/admin.phtml
:
<?php $layout('layout::default', $layoutData ?? []); ?>
<div class="admin-wrapper">
<aside class="admin-sidebar">
<!-- Admin navigation -->
<nav>
<ul>
<li><a href="<?= $url('admin::dashboard') ?>">Dashboard</a></li>
<li><a href="<?= $url('admin::users') ?>">Users</a></li>
<li><a href="<?= $url('admin::settings') ?>">Settings</a></li>
</ul>
</nav>
</aside>
<main class="admin-content">
<?= $content ?>
</main>
</div>
Extend the admin base:
<?php
$this->layout('base::admin', [
'layoutData' => [
'title' => 'Admin Dashboard',
'description' => 'Administration panel',
],
]);
?>
<h1>Dashboard</h1>
<div class="dashboard-widgets">
<!-- Dashboard content -->
</div>
Form Handling
CSRF Protection
Include CSRF tokens in forms:
<form method="post" action="<?= $url('contact::submit') ?>">
<?= $this->csrfToken() ?>
<div class="mb-3">
<label for="name" class="form-label">Name</label>
<input type="text" class="form-control" id="name" name="name" required>
</div>
<div class="mb-3">
<label for="email" class="form-label">Email</label>
<input type="email" class="form-control" id="email" name="email" required>
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
Form Validation Display
Display validation errors:
<?php if (!empty($errors)): ?>
<div class="alert alert-danger">
<ul class="mb-0">
<?php foreach ($errors as $field => $fieldErrors): ?>
<?php foreach ($fieldErrors as $error): ?>
<li><?= $this->escapeHtml($error) ?></li>
<?php endforeach; ?>
<?php endforeach; ?>
</ul>
</div>
<?php endif; ?>
Flash Messages
Display flash messages:
<?php if (!empty($flashMessages)): ?>
<?php foreach ($flashMessages as $type => $messages): ?>
<?php foreach ($messages as $message): ?>
<div class="alert alert-<?= $this->escapeHtmlAttr($type) ?> alert-dismissible fade show">
<?= $this->escapeHtml($message) ?>
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
<?php endforeach; ?>
<?php endforeach; ?>
<?php endif; ?>
Template Configuration
Template Paths
Configure template paths in config/autoload/templates.global.php
:
return [
'templates' => [
'paths' => [
'layout' => [getcwd() . '/src/Shared/templates/layout'],
'partial' => [getcwd() . '/src/Shared/templates/partial'],
'error' => [getcwd() . '/src/Shared/templates/error'],
'page' => [getcwd() . '/src/Page/templates'],
'contact' => [getcwd() . '/src/Contact/templates'],
],
],
];
Template Extensions
Register custom template extensions:
// In module ConfigProvider
return [
'templates' => [
'extension' => 'phtml', // Default extension
'paths' => [
'module' => [__DIR__ . '/templates'],
],
],
];
Performance Optimization
Template Caching
Enable template caching in production:
// config/autoload/templates.production.php
return [
'templates' => [
'cache' => [
'enabled' => true,
'path' => 'var/cache/templates',
],
],
];
Asset Optimization
Optimize assets in templates:
<!-- Minified CSS in production -->
<?php if ($this->isProduction()): ?>
<link href="<?= $this->asset('/css/app.min.css') ?>" rel="stylesheet">
<?php else: ?>
<link href="<?= $this->asset('/css/app.css') ?>" rel="stylesheet">
<?php endif; ?>
<!-- Deferred JavaScript -->
<script src="<?= $this->asset('/js/app.js') ?>" defer></script>
Security Best Practices
Always Escape Output
<!-- GOOD -->
<h1><?= $this->escapeHtml($title) ?></h1>
<!-- BAD -->
<h1><?= $title ?></h1>
Validate Template Data
// In handler
$data = [
'title' => $this->validateTitle($request->getQueryParams()['title'] ?? ''),
'content' => $this->sanitizeContent($content),
];
return new HtmlResponse($this->template->render('page::view', $data));
Use CSRF Tokens
<!-- Always include CSRF tokens in forms -->
<form method="post">
<?= $this->csrfToken() ?>
<!-- Form fields -->
</form>
Next Steps
- Domain Layer - Domain-Driven Design patterns
- Configuration - Advanced configuration
- Development - Development workflow
- Deployment - Production deployment