Мой родной PHP шаблонизатор

Целью этой статьи является разработка простого шаблонизатора, который смог бы удовлетворить основные потребности разработчика в моём лице, да и не только в моём. Принцип работы его будет основан на native методе, который вместе с другим детальнее описан тут для тех, кто еще не в курсе.

Итак, какой функционал я хочу? Для начала, это должно быть довольно простое решение без всяких рюшиков типа "куча хелперов". Конечно, кому-то это нужно, но у меня обычно всё проще, поэтому обойдёмся. То есть, должно быть независимое решение в виде одного класса. Сразу скажу, что кеширование результатов тоже упустим, дабы не отвлекаться от сути, да и задача эта в большинстве случаев принадлежит другим. Кому надо, легко может его прикрутить как внутри так и снаружи, используя этот материал. Далее я также хочу спокойно выполнять такие вещи как:

Удобное извлечение переменных в шаблоне
Вставка шаблона в шаблон
Корректная работа с HTML кодом
Режим отладки
Этим списком, думаю, я буду полностью удовлетворен.

Начнем с переменных. Тут, как и везде далее все просто - магический метод __get нам поможет обращаться к переменной по ее имени. Всё начинает выглядеть примерно так:


Listing №1 (PHP)
class Template {
  /**
   * @var array
   */
  private $_Vars;
  /**
   * @var string
   */
  private $_Name;
  /**
   * @var string
   */
  private $_Path;
 
  public function __construct()
  {
    $this->_Name = '';
    $this->_Vars = array();
    $this->_Path = '';
  }
  /**
   * @param string $VarName имя переменной
   * @param string $VarValue значение переменной
   * @return Template
   */
  public function AddVar($VarName, $VarValue)
  {
    if ($VarName !== '') {
      $this->_Vars[$VarName] = $VarValue;
    }
    return $this;
  }
  /**
   * @param string $Varname имя переменной
   * @return mixed
   */
  public function __get($VarName)
  {
    if (array_key_exists($VarName, $this->_Vars)) {
      if ($this->_Vars[$VarName] instanceof Template) {
        return $this->_Vars[$VarName]->Prepare();
      }
      else{
        return $this->_Vars[$VarName];
      }
    }
    return NULL;
  }
  /**
   * @var string $Name имя шаблона
   */
  public function SetName($Name)
  {
    $this->_Name = $Name;
  }
  /**
   * @var string $Path путь к шаблону(ам)
   */
  public function SetPath($Path)
  {
    $this->_Path = $Path;
  }
}


Теперь двумя следующими методами обеспечим корректность выводимых данных. Первый заботиться об спецсимволах HTML, а второй о валидности ccылок:


Listing №2 (PHP)
  /**
   * @param mixed $Var
   * @return mixed
   */
  protected function EscapeHtml($Var)
  {
    if(is_array($Var)) {
      foreach($Var as &$VarItem) {
        $VarItem = $this->EscapeHtml($VarItem);
      }
    }
    else {
      $Var = htmlspecialchars($Var, ENT_QUOTES);
    }
    return $Var;
  }
  /**
   * @param mixed $Var
   * @return mixed
   */
  protected function EscapeUrl($Var)
  {
    if(is_array($Var)) {
      foreach($Var as &$VarItem) {
        $VarItem = $this->EscapeUrl($Var);
      }
    }
    else {
      $Var = htmlentities($Var, ENT_QUOTES);
    }
    return $Var;
  }


Идем дальше.

Что я подразумеваю под словами "режим отладки"? Это, скорее всего, больше похоже на режим удобного чтения. В таком режиме на выходе мы получим код шаблона в том же виде, что и до его парсинга, то есть со всеми табуляциями, переводами строк и прочей нечистью. Это довольно удобно, потому что native шаблоны могут сложно восприниматься и то и дело приходится всё табулировать и переносить. В противоположность этому режиму будет обычный рабочий режим, где ненужные символы будут удалены за ненадобностью. За это будет отвечать метод _Zip, а за переключение между режимами статический метод SetDebug:


Listing №3 (PHP)
  /**
   * Свойство хранящее текущий режим
   * @static
   * @var bool
   */
  private static $_Debug = FALSE;
  /**
   * Вкл/выкл дебаг режим
   * @static
   * @param bool $TrueOrFalse TRUE
   */
  public static function SetDebug($TrueOrFalse = TRUE)
  {
    self::$_Debug = $TrueOrFalse;
  }
  /**
   * Возвращает $Text с удаленными "\t", "\n" и "\r"
   * @param  string $Text
   * @return string
   */
  private function _Zip($Text)
  {
    return (empty($Text)) ? $Text : str_replace(array("\t", "\n", "\r"), '', $Text);
  }


Теперь нам нужен метод Prepare, который выполнит всю основную работу, но не выведет данные, а возвратит их строкой. Это нам понадобиться для реализации вставки шаблона в шаблон, которая и происходит при извлечении переменной в шаблоне (метод __get). Думаю, без буферизации вывода тут не обойтись:


Listing №4 (PHP)
  /**
   * @return string
   */
  public function Prepare()
  {
    if (file_exists($this->_Path . $this->_Name)) {
      ob_start();
      ${__CLASS__} = $this;
      include $this->_Path . $this->_Name;
      unset(${__CLASS__});
      return (self::$_Debug) ? ob_get_clean() : $this->_Zip(ob_get_clean());
    }
    else {
      throw new Exception('Template file "' . $this->_Path . $this->_Name . '" does not exists');
    }
  }


Сначала проверяем наличие шаблона. Затем "по родному" парсим шаблон и возвращаем контент из буфера, очистив ненужные символы, если это необходимо. Также, меня очень напрягает писать в шаблонах $this, поэтому я решил писать там $Template. Мне так очень нравится.

Наконец, добавив метод непосредственного вывода, получаем нужный "полный фарш":


Listing №5 (PHP)
class Template {
  /**
   * @var string
   */
  private $_Name;
  /**
   * @var array
   */
  private $_Vars;
  /**
   * @var string
   */
  private $_Path;
  /**
   * @static
   * @var bool
   */
  private static $_Debug = FALSE;
  /**
   * @static
   * @param bool $TrueOrFalse TRUE
   */
  public static function SetDebug($TrueOrFalse = TRUE)
  {
    self::$_Debug = $TrueOrFalse;
  }
 
  public function __construct()
  {
    $this->_Name = '';
    $this->_Vars = array();
    $this->_Path = '';
  }
  /**
   * @param string $VarName
   * @param string $VarValue
   * @return Template
   */
  public function AddVar($VarName, $VarValue)
  {
    if ($VarName !== '') {
      $this->_Vars[$VarName] = $VarValue;
    }
    return $this;
  }
  /**
   * @param string $Varname
   * @return mixed
   */
  public function __get($VarName)
  {
    if (array_key_exists($VarName, $this->_Vars)) {
      if ($this->_Vars[$VarName] instanceof Template) {
        return $this->_Vars[$VarName]->Prepare();
      }
      else{
        return $this->_Vars[$VarName];
      }
    }
    return NULL;
  }
  /**
   * @var string $Name
   */
  public function SetName($Name)
  {
    $this->_Name = $Name;
  }
  /**
   * @var string $Path
   */
  public function SetPath($Path)
  {
    $this->_Path = $Path;
  }
  /**
   * @return string
   */
  public function Prepare()
  {
    if (file_exists($this->_Path . $this->_Name)) {
      ob_start();
      ${__CLASS__} = $this;
      include $this->_Path . $this->_Name;
      unset(${__CLASS__});
      return (self::$_Debug) ? ob_get_clean() : $this->_Zip(ob_get_clean());
    }
    else {
      throw new Exception('Template file "' . $this->_Path . $this->_Name . '" does not exists');
    }
  }
 
  public function Display()
  {
    print ($this->Prepare());
  }
  /**
   * @param mixed $Var
   * @return mixed
   */
  protected function EscapeHtml($Var)
  {
    if(is_array($Var)) {
      foreach($Var as &$VarItem) {
        $VarItem = $this->EscapeHtml($VarItem);
      }
    }
    else {
      $Var = htmlspecialchars($Var, ENT_QUOTES);
    }
    return $Var;
  }
  /**
   * @param mixed $Var
   * @return mixed
   */
  protected function EscapeUrl($Var)
  {
    if(is_array($Var)) {
      foreach($Var as &$VarItem) {
        $VarItem = $this->EscapeUrl($Var);
      }
    }
    else {
      $Var = htmlentities($Var, ENT_QUOTES);
    }
    return $Var;
  }
  /**
   * @param  string $Text
   * @return string
   */
  private function _Zip($Text)
  {
    return (empty($Text)) ? $Text : str_replace(array("\t", "\n", "\r"), '', $Text);
  }
}


Ну и напоследок, небольшой пример использования.

Допустим, у нас есть два шаблона - главная страница page.php и список пользователей userslist.php. Нужно отобразить этот список вместе с информацией на главной странице. Поскупившись на контент, рисуем главную:


Listing №6 (PHP)
<div style="border: 1px solid #E0E3F3;">
  <?php echo $Template->UserList;?>
</div>

<a href="<?php echo $Template->EscapeUrl($Template->Link)?>">About</a>

А также список пользователей:


Listing №7 (PHP)
<div>
  <?php foreach($Template->EscapeHtml($Template->UserNames) as $UserName) { ?>
  <span style="color: #FF0000;"><?php echo $UserName;?></span><br>
  <?php } ?>
</div>


За логику отвечает некая модель:


Listing №8 (PHP)
//Подключаем шаблонизатор
require 'Template.php';
//Устанавливаем режим отладки включенным для всех шаблонов
Template::SetDebug(TRUE);
//Инициализируем подшаблон списка пользователей
$UsersListView = new Template();
$UsersListView->SetName('userslist.html');
//Список пользователей
$UserNames = array('Den Rights©', 'Dasha Nasha', 'Samuil Kakojto');
$UsersListView->AddVar('UserNames', $UserNames);
//Инициализируем главный шаблон страницы
$PageView = new Template();
$PageView->SetName('page.html');
$PageView->AddVar('UserList', $UsersListView);
$PageView->AddVar('Link', 'http://www.itdumka.com.ua/index.php?cmd=shownode&node=1');
$PageView->Display();



В результате работы с включенным режимом отладки, в отличие от обычного режима, где все будет в одну строку, мы увидим такую картину:


Listing №9 (HTML)
<div style="border: 1px solid #E0E3F3;">
  <div>
    <span style="color: #FF0000;">Den Rights©</span><br>
    <span style="color: #FF0000;">Dasha Nasha</span><br>
    <span style="color: #FF0000;">Samuil Kakojto</span><br>
  </div>
</div>
<a href="http://www.itdumka.com.ua/index.php?cmd=shownode&node=1">About</a>


Вот так.
Теги:
Template View, шаблонизатор, шаблон, native шаблонизатор
Добавлено: 08 Августа 2018 07:45:41 Добавил: Андрей Ковальчук Нравится 0
Добавить
Комментарии:
Нету комментариев для вывода...