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

7.3 KiB
Raw Permalink Blame History

title date draft tags
📃 Простой класс шаблонизатора на PHP 2023-10-05T06:12:03+03:00 false
php
tutorial

Перевод: 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
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
include 'Template.php';
Template::view('index.html');
?>

Создайте новый HTML файл layout.html и вставьте в него следующий код:

<!DOCTYPE html>
<html>
    <head>
        <title>{% yield title %}</title>
        <meta charset="utf-8">
    </head>
    <body>
    {% yield content %}
    </body>
</html>

Это макет, который мы будем использовать для данного примера.

Создайте новый HTML файл index.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 следующим образом:

Template::view('about.html', [
    'title' => 'Home Page',
    'colors' => ['red','blue','green']
]);

И используйте переменные таким образом:

{% 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.

Наследование блоков:

{% block content %}
@parent
<p>Extends content block!</p>
{% endblock %}

Импортировать дополнительные файлы шаблонов:

{% include forms.html %}

Чтобы очистить кеш, достаточно удалить все файлы в катоалоге $cache_path или вызвать функцию `Template::clearCache();``

Заключение

Шаблонизатор очень полезен в больших проектах. Он позволит отделить дизайн-код от кода приложения.

Вы можете свободно использовать этот класс шаблонов в своих проектах.

Автор: David Adams | MIT License