A secure template engine for PHP with native syntax and context-aware escaping, built on top of dzentota/template-variable and dzentota/typedvalue.
🔒 Security First: Built following the AppSec Manifesto principles
- Context-aware automatic escaping (HTML, attributes, JavaScript, CSS, URLs)
- Protection against XSS attacks
- Secure by default configuration
- Template security auditing
🚀 Native PHP Syntax: Similar to bareui, uses familiar PHP syntax
- No custom template language to learn
- Full PHP power when needed
- Clean, readable templates
⚡ Performance:
- Optional template caching
- Optimized for production use
- Minimal overhead
🛡️ Developer Experience:
- Comprehensive error handling
- Debug mode for development
- Template includes and partials
- Global variables support
- Namespaced template directories
composer require dzentota/template-engineNote: This package depends on development versions of dzentota/template-variable and dzentota/typedvalue. You may need to adjust your minimum-stability setting in composer.json to "dev" or use --prefer-source flag during installation.
<?php
use Dzentota\TemplateEngine\TemplateEngine;
// Initialize the engine
$engine = new TemplateEngine([
'auto_escape' => true,
'debug' => true
]);
// Add template directory
$engine->addPath(__DIR__ . '/templates');
// Render template with data
echo $engine->render('welcome', [
'title' => 'Hello World',
'user' => ['name' => 'John Doe']
]);Template (templates/welcome.php):
<!DOCTYPE html>
<html>
<head>
<title><?= $title ?></title>
</head>
<body>
<h1>Welcome, <?= $user['name'] ?>!</h1>
</body>
</html>The template engine wraps all variables as TemplateVariable instances, which provide automatic escaping through magic methods:
<?= $variable ?>- Uses__toString()for HTML context escaping<?= $variable('attr') ?>- Uses__invoke('attr')for attribute context escaping<?= $variable('js') ?>- Uses__invoke('js')for JavaScript context escaping
The template engine automatically escapes variables based on their output context:
<!-- HTML Context (default via __toString magic method) -->
<p><?= $userInput ?></p>
<!-- Attribute Context -->
<input value="<?= $userInput('attr') ?>">
<!-- JavaScript Context -->
<script>var data = <?= $userInput('js') ?>;</script>
<!-- CSS Context -->
<style>.class { color: <?= $userInput('css') ?>; }</style>
<!-- URL Context -->
<a href="<?= $userInput('url') ?>">Link</a>
<!-- Raw Output (use with extreme caution) -->
<div><?= $trustedContent('raw') ?></div>All variables are automatically escaped unless explicitly marked as raw:
$data = [
'safe_text' => 'Hello World',
'unsafe_html' => '<script>alert("XSS")</script>'
];
// This is safe - script tags will be escaped
echo $engine->render('template', $data);Template:
<p><?= $unsafe_html ?></p>
<!-- Output: <script>alert("XSS")</script> --><!-- Escaped output (automatic via __toString) -->
<?= $variable ?>
<!-- Different contexts -->
<?= $variable('attr') ?> <!-- For HTML attributes -->
<?= $variable('js') ?> <!-- For JavaScript -->
<?= $variable('css') ?> <!-- For CSS values -->
<?= $variable('url') ?> <!-- For URLs -->
<?= $variable('raw') ?> <!-- Raw/unescaped (use with caution) -->Use native PHP syntax:
<!-- Conditionals -->
<?php if ($user['is_admin']): ?>
<p>Admin panel available</p>
<?php endif; ?>
<!-- Loops -->
<?php foreach ($items as $item): ?>
<div><?= $item['name'] ?></div>
<?php endforeach; ?>
<!-- Switch statements -->
<?php switch ($user['role']): ?>
<?php case 'admin': ?>
<p>Administrator</p>
<?php break; ?>
<?php case 'user': ?>
<p>Regular user</p>
<?php break; ?>
<?php endswitch; ?><!-- Include other templates -->
<?= $include('partials/header', ['title' => 'Page Title']) ?>
<!-- Content here -->
<?= $include('partials/footer') ?>// Set global variables
$engine->addGlobal('app_name', 'My App');
$engine->addGlobal('version', '1.0.0');
// Use in templates
<p><?= $app_name ?> v<?= $version ?></p>$engine = new TemplateEngine([
'auto_escape' => true, // Enable automatic escaping
'strict_variables' => true, // Throw errors for undefined variables
'debug' => false, // Enable debug mode
'cache' => true, // Enable template caching
'cache_dir' => '/tmp/templates', // Cache directory
'default_context' => 'html', // Default escaping context
'file_extension' => '.php' // Template file extension
]);$engine->addPath('/path/to/templates');$engine->addPath('/path/to/app/templates', 'app');
$engine->addPath('/path/to/admin/templates', 'admin');
// Use namespaced templates
echo $engine->render('@admin/dashboard');
echo $engine->render('@app/welcome');The template engine includes security auditing capabilities:
use Dzentota\TemplateEngine\Security\SecurityManager;
$security = new SecurityManager();
// Audit a template
$templateSource = file_get_contents('template.php');
$audit = $security->auditTemplate($templateSource, 'template.php');
echo "Security Score: " . $audit['security_score'] . "/100\n";
foreach ($audit['issues'] as $issue) {
echo "- " . $issue['message'] . " (Severity: " . $issue['severity'] . ")\n";
}use Dzentota\TemplateEngine\Security\SecurityManager;
$security = new SecurityManager([
'strict_mode' => true,
'allow_php_functions' => false,
'max_template_size' => 1024 * 1024
]);
$engine = new TemplateEngine(['security_manager' => $security]);$security = new SecurityManager();
$headers = $security->generateCSPHeaders();
foreach ($headers as $name => $value) {
header("$name: $value");
}$template = $engine->load('welcome');
$metadata = $template->getMetadata();
echo "Template: " . $metadata['name'] . "\n";
echo "Size: " . $metadata['size'] . " bytes\n";
echo "Modified: " . date('Y-m-d H:i:s', $metadata['modified']) . "\n";try {
$output = $engine->render('template', $data);
echo $output;
} catch (RuntimeException $e) {
// Template not found, rendering error, etc.
error_log("Template error: " . $e->getMessage());
echo "Template error occurred";
} catch (InvalidArgumentException $e) {
// Invalid configuration, variable names, etc.
error_log("Configuration error: " . $e->getMessage());
}<!-- ✅ Good - automatic escaping via TemplateVariable -->
<p><?= $userInput ?></p>
<!-- ❌ Bad - raw PHP variable (bypasses TemplateVariable) -->
<p><?= $_GET['user_input'] ?></p><!-- ✅ Good -->
<input value="<?= $value('attr') ?>">
<script>var data = <?= $data('js') ?>;</script>
<!-- ❌ Bad -->
<input value="<?= $value ?>">
<script>var data = "<?= $data ?>";</script>// In development, audit your templates
if ($developmentMode) {
$security = new SecurityManager();
$audit = $security->auditTemplate($templateSource, $templateName);
if ($audit['security_score'] < 80) {
throw new Exception("Template security score too low: " . $audit['security_score']);
}
}$engine = new TemplateEngine([
'cache' => true,
'cache_dir' => sys_get_temp_dir() . '/template_cache'
]);See the examples/ directory for complete working examples:
basic_usage.php- Basic template renderingtemplates/welcome.php- Comprehensive template with security featurestemplates/partials/- Template includes and partials
This template engine is designed with security as a primary concern:
- XSS Prevention: All output is escaped by default
- Context Awareness: Different escaping for HTML, attributes, JS, CSS, URLs
- Directory Traversal Protection: Templates cannot access files outside designated directories
- Function Restrictions: Dangerous PHP functions are blocked in templates
- Security Auditing: Built-in tools to audit template security
- PHP 8.1 or higher
dzentota/template-variable(dev-main)dzentota/typedvalue(dev-master)
MIT License - see LICENSE file for details.
- Fork the repository
- Create a feature branch
- Add tests for new functionality
- Ensure all tests pass
- Submit a pull request
For issues and questions:
- GitHub Issues: Report a bug
- Security Issues: Email [email protected]