Предлагаю вашему вниманию свое решение по генерации html на PHP. Задача вроде бы тривиальная, но хотелось бы, чтобы это было расширяемо, кратко, но в тоже время с хорошим функционалом. Получилось вроде не плохо.

Сразу скажу( как многие считают в комментариях), что задача ставилось не написать шаблонизатор (которых и так много) и не заменить шаблонизатор JavaScript. Я прекрасно знаю, что true way это разделять html и данные. Но мне понадобилось писать html в классах, для создания компонентов фреймворка, на подобие CGridView в yii, стоит ли в таких местах выносить html в отдельные файлы решать вам.

Основная цель, избавится от html в классах и функциях.

Простой пример, обычная кнопка:

CHtml::create()
    ->p()
        ->a(array('href' => 'http://habrahabr.ru', 'class' => 'btn'))
            ->text('Перейти')
    ->render();

Результат:

<p><a href="http://habrahabr.ru" class="btn">Перейти</a></p>



Ничего хитрого, можно было бы этим и ограничется, но захотелось циклы:

$arr = array('1' => 'Первый', '2' => 'Второй');

CHtml::create()
	 ->select($options)
		->each(CHtml::plainArray($arr, 'value', 'text'))
			->option('array("value" => $data->value)')
				 ->text('$data->text')
		->end()
	->endEach()


Тут понадобилось вызвать функцию plainArray() которая превращает массив в виде:
$arr = array(
    array('value' => '1', 'text' =>'Первый'), 
    array('value' => '2', 'text' => 'Второй')
);

Теги внутри цикла могут содержать функции или строки с eval выражениями, вложенность любая, пример с таблицей:

$columns = array(
    array('id' => 'NAME', 'label' => 'Имя'),
    array('id' => 'AGE', 'label' => 'Возраст')
);

$data = array(
    array('NAME' => 'Петр', 'AGE' => 29),
    array('NAME' => 'Василий', 'AGE' => 32)
);

CHtml::create()
	->table()
		->thead()
			 ->tr()
			->each($columns)
				 ->th()
					->text(function($column){
						 return $column['label'];
					})
                                 ->end()
			->endEach()
 			->end()
		->end()
			->tbody()
			->each($data)
				->tr()
				->each($columns)
					->td()
						->text(function($row, $column) {
	                                              return $row[$column['id']];
						})
					->end()
				->endEach()
				->end()
			->endEach()
->render();


Незакрытые теги закрываются автоматически.

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


class CMyHtml extends CHtml {

	public function a($options = array()) {
		$default = array(
			'href' => 'javascript:void(0)'
		);
		return parent::a(array_replace($default, $options));
	}
}



class CForm {
        
        private $_lastLabel = '';
	public function __construct(CModel $model, CHtml $html = null) {
		$this->_model = $model;
		$this->_html = $html ?: CHtml::create();
	}

	public function __call($method, $ps) {
		$options = $ps ? $ps[0]: array();

		if ($method === 'label') {
			$this->_lastLabel = isset($options['for']) ? $this->_model->getLabel($options['for']) : '';
		}

		if ($method === 'text' && $this->_lastLabel) {
			$options = $options ?: $this->_lastLabel;
			$this->_lastLabel = '';
		}
               $this->_html->$method($options);
	       return $this;
       }
}


Само решение можно посмотреть и попробовать на github.

Спасибо за внимание.