Blog/content/posts/2023/php/lightweight-template-engine-php/index.md

231 lines
7.3 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
title: "📃 Простой класс шаблонизатора на PHP"
date: 2023-10-05T06:12:03+03:00
draft: false
tags: [php, tutorial]
---
![](splash.png)
> Перевод: https://codeshack.io/lightweight-template-engine-php/
Класс шаблонизатора является лёгким, гибким, быстрым
и безопасным. Он компилирует шаблоны в оптимизированный PHP код.
Ниже я предоставлю полный исходный код и примеры того,
как можно использовать класс шаблонизатора.
# Зачем мне нужен шаблонизатор?
The template engine keeps your design code away from your application code,
this reason alone is good practice and follows many design patterns.
Using a template engine is entirely up to you, if you prefer
to keep your code clean and tidy then using a template engine is ideal.
If you're working with the MVC pattern then it is a good idea
to use a template engine.
# Исходный код
Create a new file and name it `Template.php` and add:
Создайте новый файл `Template.php` и вставьте в него следующий код:
```php
<?php
class Template {
static $blocks = array();
static $cache_path = 'cache/';
static $cache_enabled = FALSE;
static function view($file, $data = array()) {
$cached_file = self::cache($file);
extract($data, EXTR_SKIP);
require $cached_file;
}
static function cache($file) {
if (!file_exists(self::$cache_path)) {
mkdir(self::$cache_path, 0744);
}
$cached_file = self::$cache_path . str_replace(array('/', '.html'), array('_', ''), $file . '.php');
if (!self::$cache_enabled || !file_exists($cached_file) || filemtime($cached_file) < filemtime($file)) {
$code = self::includeFiles($file);
$code = self::compileCode($code);
file_put_contents($cached_file, '<?php class_exists(\'' . __CLASS__ . '\') or exit; ?>' . PHP_EOL . $code);
}
return $cached_file;
}
static function clearCache() {
foreach(glob(self::$cache_path . '*') as $file) {
unlink($file);
}
}
static function compileCode($code) {
$code = self::compileBlock($code);
$code = self::compileYield($code);
$code = self::compileEscapedEchos($code);
$code = self::compileEchos($code);
$code = self::compilePHP($code);
return $code;
}
static function includeFiles($file) {
$code = file_get_contents($file);
preg_match_all('/{% ?(extends|include) ?\'?(.*?)\'? ?%}/i', $code, $matches, PREG_SET_ORDER);
foreach ($matches as $value) {
$code = str_replace($value[0], self::includeFiles($value[2]), $code);
}
$code = preg_replace('/{% ?(extends|include) ?\'?(.*?)\'? ?%}/i', '', $code);
return $code;
}
static function compilePHP($code) {
return preg_replace('~\{%\s*(.+?)\s*\%}~is', '<?php $1 ?>', $code);
}
static function compileEchos($code) {
return preg_replace('~\{{\s*(.+?)\s*\}}~is', '<?php echo $1 ?>', $code);
}
static function compileEscapedEchos($code) {
return preg_replace('~\{{{\s*(.+?)\s*\}}}~is', '<?php echo htmlentities($1, ENT_QUOTES, \'UTF-8\') ?>', $code);
}
static function compileBlock($code) {
preg_match_all('/{% ?block ?(.*?) ?%}(.*?){% ?endblock ?%}/is', $code, $matches, PREG_SET_ORDER);
foreach ($matches as $value) {
if (!array_key_exists($value[1], self::$blocks)) self::$blocks[$value[1]] = '';
if (strpos($value[2], '@parent') === false) {
self::$blocks[$value[1]] = $value[2];
} else {
self::$blocks[$value[1]] = str_replace('@parent', self::$blocks[$value[1]], $value[2]);
}
$code = str_replace($value[0], '', $code);
}
return $code;
}
static function compileYield($code) {
foreach(self::$blocks as $block => $value) {
$code = preg_replace('/{% ?yield ?' . $block . ' ?%}/', $value, $code);
}
$code = preg_replace('/{% ?yield ?(.*?) ?%}/i', '', $code);
return $code;
}
}
?>
```
Когда код проекта будет готов, не забудьте включить кеширование,
обновив переменные `$cache_enabled` и `$cache_path variables`.
# Как использовать шаблонизатор
Создайте новый файл `index.php` и вставьте в него следующий код:
```php
<?php
include 'Template.php';
Template::view('index.html');
?>
```
Создайте новый HTML файл `layout.html` и вставьте в него следующий код:
```html
<!DOCTYPE html>
<html>
<head>
<title>{% yield title %}</title>
<meta charset="utf-8">
</head>
<body>
{% yield content %}
</body>
</html>
```
Это макет, который мы будем использовать для данного примера.
Создайте новый HTML файл `index.html` и вставьте в него следующий код:
```html
{% extends layout.html %}
{% block title %}Home Page{% endblock %}
{% block content %}
<h1>Home</h1>
<p>Welcome to the home page!</p>
{% endblock %}
```
Теперь откройте файл `index.php` и посмотрите на результат.
Но что, если мы хотим использовать переменные в наших файлах шаблонов?
Просто измените код шаблона в `index.php` следующим образом:
```php
Template::view('about.html', [
'title' => 'Home Page',
'colors' => ['red','blue','green']
]);
```
И используйте переменные таким образом:
```html
{% extends layout.html %}
{% block title %}{{ $title }}{% endblock %}
{% block content %}
<h1>Home</h1>
<p>Welcome to the home page, list of colors:</p>
<ul>
{% foreach($colors as $color): %}
<li>{{ $color }}</li>
{% endforeach; %}
</ul>
{% endblock %}
```
Чтобы обезопасить результат, вместо: `{{ $output }}`
необходимо использовать `{{{ $output }}}`.
Для таких значений, будет применяться функция
[`htmlspecialchars`](https://www.php.net/manual/en/function.htmlspecialchars.php).
Наследование блоков:
```html
{% block content %}
@parent
<p>Extends content block!</p>
{% endblock %}
```
Импортировать дополнительные файлы шаблонов:
```text
{% include forms.html %}
```
Чтобы очистить кеш, достаточно удалить все файлы в катоалоге `$cache_path`
или вызвать функцию `Template::clearCache();``
# Заключение
Шаблонизатор очень полезен в больших проектах.
Он позволит отделить дизайн-код от кода приложения.
Вы можете свободно использовать этот класс шаблонов в своих проектах.
**Автор:** David Adams | **MIT License**