Как написать шаблонизатор на php​После статьи Как написать свой фреймворк на php, один знакомый программист задал мне такой вопрос: а как работает шаблонизатор в Yii, а конкретно функция render("filename", $variables = array())? 

Если углубиться в историю php (а он был написан как шаблонизатор в языке perl), то будет ясно, лучшего шаблонизатора, чем сам язык на нем не написать. Smarty и т.п. библиотеки,  ограничены своим API. И работа с ними напоминает история про Active Record, которую я описал в предыдущей статье.

Итак, как можно написать простейший шаблонизатор, с тем же функционалом, что и в Yii.

Однажды, на страницах  этого блога, уже была статья, про такой шаблонизатор. Однако, там был обзор готовой библиотеки. Сейчас, я расскажу подробнее о том, как оно работает. 

Создадим класс, и сразу опишем методы, которыми он будет владеть.

class tpl{
 static function render($filename,$variables=array(),$output=true){}
 static function renderPartial($filename,$variables=array(),$output=true){}
}

с этими методами знаком программист работающий с Yii. Метод render выводит всю страницу, включая в специально отведенное место содержимое файла $filename. Метод renderPartial  просто выводит содержимое файла $filename на экран. По своей сути, метод render это последовательный renderPartial($filename) и renderPartial('index'). Следовательно, для работы шаблонизатора, нам потребуется описать этот метод. 

Основа его работы простое подключение требуемого файла методом include, а чтобы переменные были доступны в нем самостоятельно, а не как части массива, используется подзабытая функция extract

static public function renderPartial($filename,$variables=array(),$output=true){
  extract($variables);
   
  if( file_exists(ROOT.'tpls/'.str_replace('..','',$filename).'.php') ){
    if( !$output )
      ob_start();
    include ROOT.'tpls/'.$filename.'.php'
    return !$output?ob_get_clean():true;
  } else
​   throw new Exception('File '. ROOT.'tpls/'.$filename.'.php not found');
 
}

Чтобы загнать содержимое файла в переменную, используется известный прием с ob_start() ob_get_clean(). Это такие функции, которые загоняют стандартный поток вывода в другие рельсы. Говоря другим языком, они буферизируют вывод. Подобно тому, как в windows данные помещаются в буфер обмена. Подробнее про это можно почитать здесь 

 т.е. если  параметр 

$output

оставить без изменений, то они и не задействуются. Но если его установить в false, то все, что выведет файл $filename.php не пойдет в основной поток, а будет собрано и возвращено методом renderPartial. Не правда ли, очень удобно!?

Вот и вся магия. Теперь опишем метод render

static public function render($filename,$variables=array(),$output=true){
  $content = self::renderPartial($filename,$variables,false);
  return self::renderPartial('main',array_merge(array('content'=>$content),$variables),$output);
}

Как  и писал выше, дважды вызываем метод renderPartial. Сперва с output==false, для целевого файла. Второй раз вызываем с тем же, что был подан в сам render. В файле main.php, содержимое файла $filename.php будет доступно в переменной $content.

Это не самое веселое, что можно сделать. Что, если в переменную $content загонять экземпляр класса, с созданным методом __toString. Можно будет сделать рендер в нормальном порядке. Сперва main а затем $filename в нем. Так как и должно быть. 

Но оставлю это для вашей фантазии.

В рамках статей о написании идеального велосипеда фреймворка добавим в класс контроллера, эти два метода. Отличать их будет только параметр static, перед названием метода. Он нам больше не понадобится. Так как для каждого контроллера будет создан свой экземпляр. 

<?php
class Controller extends Singleton{
  function __call( $methodName,$args=array() ){
    if( is_callable( array($this,$methodName) ) )
      return call_user_func_array(array($this,$methodName),$args);
    else
      throw new Except('In controller '.get_called_class().' method '.$methodName.' not found!');
  }
   
  public $tplPath = '';
  public $tplControllerPath = '';
   
  function __construct(){
    $this->tplPath = APP.'views/';
    $this->tplControllerPath = APP.'views/'.strtolower(str_replace('Controller','',get_called_class())).'/';
  }
   
  private function _renderPartial($fullpath,$variables=array(),$output=true){
    extract($variables);
     
    if( file_exists($fullpath) ){
      if( !$output )
        ob_start();
      include $fullpath;
      return !$output?ob_get_clean():true;
    }else
      throw new Except('File '.$fullpath.'.php not found');
     
  }
  /**
   * renderPartial - метод доступный в контроллере, для вывода файла шаблона.
   * Не запускает больше никаких файлов. Удобен при ajax вызове контроллера
   *
   * @params $filename - название шаблона в папке views/название контроллера/{}.php
   * @params $variables - ключи массива будут доступны в шаблоне как переменные с
   * теми же именами
   * @params $output - если указать false, то данные из шаблона не будут выведены в основной поток а будут возвращены методом
   */
  public function renderPartial($filename,$variables=array(),$output=true){
    $file = $this->tplControllerPath.str_replace('..','',$filename).'.php';
    return $this->_renderPartial($file,$variables,$output);
  }
   
  /**
   * render - метод выполняет полный вывод страницы на экран. При этом в нее включается
   * содержимое файла шаблона $filename
   *
   * @params - все параметры идентичны renderPartial
   */
  public function render($filename,$variables=array(),$output=true){
    $content = $this->renderPartial($filename,$variables,false);
    return $this->_renderPartial($this->tplPath.'main.php',array_merge(array('content'=>$content),$variables),$output);
  }
}

Вы все еще не форкнули этот фреймворк? Чего же вы ждете? Давайте сделаем идеальный фреймворк вместе.

Оставлять комментарии могут только зарегистрированные пользователи

Комментарии  

yesworld
# yesworld 13.08.2014 23:31
Доброго времени суток.

Я как раз искал что-то подобное, спасибо вам огромное за этот труд!



Вот только я не совсем понял, зачем нужны:

ob_start(); и ob_get_clean();



Спасибо, за внимание. = )
Leroy
# Leroy 14.08.2014 18:06
ob_start и ob_get_clean­ нужны для того чтобы получить то что вывел файл file.php при выполнении в переменную
yesworld
# yesworld 15.08.2014 13:16
Ну хорошо, если мы передадим false 3м параметром, то сработают эти функции и они выведут содержимое файла.

Если передать true будет ровно тоже самое.



Я понимаю в принципе как работают функции буфера, но понять не могу, зачем они здесь, так как в независимости от 3 переменной, будет один и тот же результат.



Или я чего то не понимаю ))
Leroy
# Leroy 15.08.2014 17:05
$a = $this->renderPartial('file',array(),false);// ничего не выводиться
echo str_replace('Вася','Петя',$a);


или так
$this->renderPartial('file',array(
'text'=>$this->renderPartial('item',array(),false)
));


ob_get_clean­ отчищает вывод после ob_start
yesworld
# yesworld 20.08.2014 13:55
Я просто до этого понять не мог, когда это может пригодиться...

Спасибо вам, что объяснили!
kaokao
# kaokao 07.07.2022 15:33
pg slot loginทางเข้าแห่งความหรรษา ที่ทุกท่านจะได้รับหลังจากที่เข้าสู่ระบบ กับเว็บ สล็อต ออนไลน์ของเรา กับ สล็อตออนไลน์ที่ดีที่สุด แตกบ่อยที่สุด ปลอดภัยที่สุดวนปี 2022 นี้