Как написать шаблонизатор на 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);
	}	
}

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

Рассказать друзьям

Добавить комментарий


Защитный код
Обновить

Комментарии   

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

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



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

ob_start(); и ob_get_clean();



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

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



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



Или я чего то не понимаю ))
0
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
0
yesworld
# yesworld 20.08.2014 13:55
Я просто до этого понять не мог, когда это может пригодиться...

Спасибо вам, что объяснили!