|
@@ -1,83 +1,230 @@
|
|
|
# PurePlate
|
|
# PurePlate
|
|
|
|
|
|
|
|
-Pure is a high-performance, lexer-based template engine for PHP 7.4+. It compiles a clean, intuitive syntax into native cached PHP code with zero runtime overhead.
|
|
|
|
|
|
|
+**A lightweight template engine for PHP. The syntax of Twig. The power of native PHP. None of the weight.**
|
|
|
|
|
|
|
|
-## Installation
|
|
|
|
|
|
|
+PurePlate parses templates with PHP's native `token_get_all()` lexer and compiles them to plain, cached PHP. No runtime overhead. No bloat. No magic.
|
|
|
|
|
+
|
|
|
|
|
+```twig
|
|
|
|
|
+{% extends "layout.tpl" %}
|
|
|
|
|
+
|
|
|
|
|
+{% block content %}
|
|
|
|
|
+ <h1>Hello, {{ user.name|upper }}!</h1>
|
|
|
|
|
+
|
|
|
|
|
+ {% if items is not empty %}
|
|
|
|
|
+ <ul>
|
|
|
|
|
+ {% foreach items as item %}
|
|
|
|
|
+ <li>{{ item.title }} — {{ item.price|number_format(2, ',', ' ') }} €</li>
|
|
|
|
|
+ {% endforeach %}
|
|
|
|
|
+ </ul>
|
|
|
|
|
+ {% endif %}
|
|
|
|
|
+{% endblock %}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## Why PurePlate
|
|
|
|
|
+
|
|
|
|
|
+### 1. Any PHP function works as a filter — out of the box
|
|
|
|
|
+
|
|
|
|
|
+In Twig, every filter must be registered:
|
|
|
|
|
+
|
|
|
|
|
+```php
|
|
|
|
|
+// Twig: you have to declare each function as a filter
|
|
|
|
|
+$twig->addFilter(new TwigFilter('upper', 'strtoupper'));
|
|
|
|
|
+$twig->addFilter(new TwigFilter('format', 'number_format'));
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+In PurePlate, every PHP function is already a filter:
|
|
|
|
|
+
|
|
|
|
|
+```twig
|
|
|
|
|
+{{ name|strtoupper }}
|
|
|
|
|
+{{ price|number_format(2, ',', ' ') }}
|
|
|
|
|
+{{ text|substr(0, 100) }}
|
|
|
|
|
+{{ items|count }}
|
|
|
|
|
+{{ date|date("Y-m-d") }}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+No registration. No wrappers. The entire PHP standard library is available immediately.
|
|
|
|
|
+
|
|
|
|
|
+### 2. Lexer-based, not regex-based
|
|
|
|
|
+
|
|
|
|
|
+Templates are tokenized with PHP's own `token_get_all()` — the same lexer PHP uses to parse its own source code. This means:
|
|
|
|
|
+
|
|
|
|
|
+- Correct handling of strings, escapes, nested quotes
|
|
|
|
|
+- No regex edge-cases that break on unusual input
|
|
|
|
|
+- Predictable, deterministic parsing
|
|
|
|
|
+
|
|
|
|
|
+### 3. Compile-time validation
|
|
|
|
|
+
|
|
|
|
|
+Generated PHP is validated with `TOKEN_PARSE` **before** being cached. If the compilation produces invalid PHP, you know immediately — not at runtime, not in production.
|
|
|
|
|
+
|
|
|
|
|
+### 4. Source-mapped errors
|
|
|
|
|
+
|
|
|
|
|
+Every compiled line carries a comment pointing back to the original template:
|
|
|
|
|
+
|
|
|
|
|
+```php
|
|
|
|
|
+<?php /*L:14;F:templates/page.tpl*/ echo htmlspecialchars(...); ?>
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+When an error happens, PurePlate rewrites the exception to point at the **template file and line**, not the cached PHP file.
|
|
|
|
|
+
|
|
|
|
|
+```
|
|
|
|
|
+PurePlate Error: Undefined variable $username [At: templates/page.tpl:14]
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### 5. Auto-escaping by default
|
|
|
|
|
+
|
|
|
|
|
+`{{ var }}` is always passed through `htmlspecialchars(..., ENT_QUOTES)`. Output is safe by default.
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
|
|
|
-You can install the library using Composer. Just run the following command:
|
|
|
|
|
|
|
+## Comparison
|
|
|
|
|
+
|
|
|
|
|
+| | Twig 3 | PurePlate |
|
|
|
|
|
+|------------------------------|-------------|-------------|
|
|
|
|
|
+| Syntax | Twig | Twig-like |
|
|
|
|
|
+| PHP function as filter | ❌ Must register | ✅ Native |
|
|
|
|
|
+| Compile-time syntax check | ❌ | ✅ |
|
|
|
|
|
+| Source-mapped errors | ⚠️ Complex | ✅ Inline |
|
|
|
|
|
+| PHP 7.4 support | ⚠️ Twig 3.x only | ✅ |
|
|
|
|
|
+| Dependencies | Several | Zero |
|
|
|
|
|
+| Auto-escape | ✅ | ✅ |
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## Installation
|
|
|
|
|
|
|
|
```bash
|
|
```bash
|
|
|
composer require michel/pure-plate
|
|
composer require michel/pure-plate
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
-## Basic Usage
|
|
|
|
|
|
|
+PHP 7.4 or higher. No other runtime dependencies.
|
|
|
|
|
|
|
|
-To use the renderer in your project, first create an instance of the `PhpRenderer` class and pass the directory where your templates are located.
|
|
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## Quick Start
|
|
|
|
|
|
|
|
```php
|
|
```php
|
|
|
-use Michel\Renderer\PhpRenderer;
|
|
|
|
|
|
|
+use Michel\PurePlate\Engine;
|
|
|
|
|
+
|
|
|
|
|
+$plate = new Engine(__DIR__ . '/templates');
|
|
|
|
|
+
|
|
|
|
|
+echo $plate->render('page.tpl', [
|
|
|
|
|
+ 'user' => ['name' => 'fady'],
|
|
|
|
|
+ 'items' => [
|
|
|
|
|
+ ['title' => 'Item A', 'price' => 19.90],
|
|
|
|
|
+ ['title' => 'Item B', 'price' => 42.00],
|
|
|
|
|
+ ],
|
|
|
|
|
+]);
|
|
|
|
|
+```
|
|
|
|
|
|
|
|
-// Specify the template directory
|
|
|
|
|
-$templateDir = '/path/to/templates';
|
|
|
|
|
|
|
+---
|
|
|
|
|
|
|
|
-// Optional global variables to be passed to all templates
|
|
|
|
|
-$globals = [
|
|
|
|
|
- 'siteTitle' => 'My Website',
|
|
|
|
|
-];
|
|
|
|
|
|
|
+## Syntax
|
|
|
|
|
|
|
|
-// Create the renderer instance
|
|
|
|
|
-$renderer = new PhpRenderer($templateDir, $globals);
|
|
|
|
|
|
|
+### Output
|
|
|
|
|
+
|
|
|
|
|
+```twig
|
|
|
|
|
+{{ variable }} {# auto-escaped #}
|
|
|
|
|
+{{ user.name }} {# same as user->name #}
|
|
|
|
|
+{{ user.getName() }} {# method call #}
|
|
|
|
|
+{{ value|filter }} {# any PHP function #}
|
|
|
|
|
+{{ value|filter(arg1, arg2) }} {# with arguments #}
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
-### Creating a Layout
|
|
|
|
|
|
|
+### Control structures
|
|
|
|
|
+
|
|
|
|
|
+```twig
|
|
|
|
|
+{% if condition %} ... {% elseif other %} ... {% else %} ... {% endif %}
|
|
|
|
|
+{% foreach items as item %} ... {% endforeach %}
|
|
|
|
|
+{% for i = 0; i < 10; i++ %} ... {% endfor %}
|
|
|
|
|
+{% while condition %} ... {% endwhile %}
|
|
|
|
|
+```
|
|
|
|
|
|
|
|
-Create a layout file (e.g., `layout.php`) that represents the common structure of your pages. Use `block()` to define sections that will be replaced by content from child templates.
|
|
|
|
|
|
|
+### Tests
|
|
|
|
|
|
|
|
-```php
|
|
|
|
|
-<!DOCTYPE html>
|
|
|
|
|
|
|
+```twig
|
|
|
|
|
+{% if list is empty %} ... {% endif %}
|
|
|
|
|
+{% if list is not empty %} ... {% endif %}
|
|
|
|
|
+{% if not active %} ... {% endif %}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### Variable assignment
|
|
|
|
|
+
|
|
|
|
|
+```twig
|
|
|
|
|
+{% set total = price * quantity %}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### Template inheritance
|
|
|
|
|
+
|
|
|
|
|
+```twig
|
|
|
|
|
+{# layout.tpl #}
|
|
|
<html>
|
|
<html>
|
|
|
-<head>
|
|
|
|
|
- <title><?php echo $this->block('title'); ?></title>
|
|
|
|
|
-</head>
|
|
|
|
|
<body>
|
|
<body>
|
|
|
- <div class="container">
|
|
|
|
|
- <?php echo $this->block('content'); ?>
|
|
|
|
|
- </div>
|
|
|
|
|
|
|
+ {% block content %}{% endblock %}
|
|
|
</body>
|
|
</body>
|
|
|
</html>
|
|
</html>
|
|
|
|
|
+
|
|
|
|
|
+{# page.tpl #}
|
|
|
|
|
+{% extends "layout.tpl" %}
|
|
|
|
|
+{% block content %}
|
|
|
|
|
+ <h1>Hello</h1>
|
|
|
|
|
+{% endblock %}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### Includes
|
|
|
|
|
+
|
|
|
|
|
+```twig
|
|
|
|
|
+{% include "partials/header.tpl" %}
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
-### Creating a Template
|
|
|
|
|
|
|
+### Comments
|
|
|
|
|
|
|
|
-Create your template file (e.g., `page.php`). Use `extend()` to specify the layout file and `startBlock()` / `endBlock()` to define the content for the blocks.
|
|
|
|
|
|
|
+```twig
|
|
|
|
|
+{# This will not appear in the output #}
|
|
|
|
|
+```
|
|
|
|
|
|
|
|
-```php
|
|
|
|
|
-<?php $this->extend('layout.php'); ?>
|
|
|
|
|
|
|
+---
|
|
|
|
|
|
|
|
-<?php $this->startBlock('title'); ?>
|
|
|
|
|
- My Page Title
|
|
|
|
|
-<?php $this->endBlock(); ?>
|
|
|
|
|
|
|
+## Dev mode
|
|
|
|
|
|
|
|
-<?php $this->startBlock('content'); ?>
|
|
|
|
|
- <h1>Hello, <?php echo $name; ?>!</h1>
|
|
|
|
|
- <p>Welcome to my website.</p>
|
|
|
|
|
-<?php $this->endBlock(); ?>
|
|
|
|
|
|
|
+In dev mode, templates are recompiled on every request:
|
|
|
|
|
+
|
|
|
|
|
+```php
|
|
|
|
|
+$plate = new Engine(__DIR__ . '/templates', devMode: true);
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
-### Rendering Templates
|
|
|
|
|
|
|
+In production (default), templates are compiled once and cached. The cache is invalidated automatically when the template source changes.
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## Cache directory
|
|
|
|
|
|
|
|
-To render your template, use the `render` method. You can pass an array of variables to be extracted and made available within the template.
|
|
|
|
|
|
|
+By default, compiled templates are stored in the system temp directory. To customize:
|
|
|
|
|
|
|
|
```php
|
|
```php
|
|
|
-echo $renderer->render('page.php', ['name' => 'John']);
|
|
|
|
|
|
|
+$plate = new Engine(
|
|
|
|
|
+ templateDir: __DIR__ . '/templates',
|
|
|
|
|
+ devMode: false,
|
|
|
|
|
+ cacheDir: __DIR__ . '/var/cache/plate'
|
|
|
|
|
+);
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
-This will render `page.php`, inject its blocks into `layout.php`, and return the final HTML.
|
|
|
|
|
|
|
+---
|
|
|
|
|
|
|
|
-## Contributing
|
|
|
|
|
|
|
+## Globals
|
|
|
|
|
+
|
|
|
|
|
+Variables passed as globals are available in every template without being re-passed at each render:
|
|
|
|
|
+
|
|
|
|
|
+```php
|
|
|
|
|
+$plate = new Engine(__DIR__ . '/templates', globals: [
|
|
|
|
|
+ 'siteTitle' => 'My Site',
|
|
|
|
|
+ 'version' => '1.0',
|
|
|
|
|
+]);
|
|
|
|
|
+```
|
|
|
|
|
|
|
|
-Contributions to the PurePlate library are welcome! If you find any issues or want to suggest enhancements, feel free to open a GitHub issue or submit a pull request.
|
|
|
|
|
|
|
+---
|
|
|
|
|
|
|
|
## License
|
|
## License
|
|
|
|
|
|
|
|
-PurePlate is open-source software released under the Mozilla Public License 2.0. See the [LICENSE](LICENSE) file for more details.
|
|
|
|
|
|
|
+Mozilla Public License 2.0
|