Свой шаблонизатор 2

В первой части мы по всем правилам ООП создали шаблонизатор, теперь наростим его мощь, будем делать это двумя способами:

  1. Расширая класс Amdy_Templater
  2. С помощью классов-помощников.

Первый способ эллементарный:

class Amdy_Templater extends Amdy_Template_Abstract {
    protected function escape($var, $mode = ENT_COMPAT) {
        return htmlspecialchars($var, $mode);
    }
}

а в шаблоне

1. teplater !<?=$this->escape($this->test, ENT_NOQUOTES)?>! teplater<br/>

можно и без второго параметра, но иногда очень важно чистить кавычки.

Второй способ позаковырестее.

Будем применять делегирование — когда свойства одного объекта передаются (делегируются) другому. Способ примерно таков:

class Foo {
    public function functionFoo() {}
}
class Bar {
    public function functionFoo() {
        $foo = new Foo();
        $foo->functionFoo();
    }
}

Т.е. мы получаем нужный объект и вызываем его метод.

Интерфейс было решено не трогать, чтобы оставить возможность минимализма для извращенцев, менял абстрактный класс.

<?php
require_once('Amdy/Templater/Interface.php');
abstract class Amdy_Template_Abstract implements Amdy_Templater_Interface {
    protected $_data = array();
    protected $templateExt = '.tpl.php';

    protected $_helper = array();
    protected $_helperMethod = array();

    public function __set($key, $value) {
        $this->_data[$key] = $value;
    }
    public function __get($key) {
        return isset($this->_data[$key]) ? $this->_data[$key] : null;
    }
    public function setTemplateExt($ext) {
        $this->templateExt = $ext;
    }
    public function display($tpl) {
        require($tpl . $this->templateExt);
    }

    public function fetch($tpl) {
        ob_start();
        $this->display($tpl);
        $retVal = ob_get_contents();
        ob_end_clean();
        return $retVal;
    }
    public function addHelper($helper) {
        $method = get_class_methods($helper);
        foreach ($method AS $name)
            if (in_array($name, $this->_helperMethod)) {
                die("ERROR in " . __METHOD__ . " method `$name` exist in other helper");
        }
        $this->_helperMethod = array_merge($this->_helperMethod, $method);
        $this->_helper[ get_class($helper) ] = $helper;
    }
    public function __call($method, $args = array()) {
        if (in_array($method, $this->_helperMethod)) {
            foreach ($this->_helper AS $helper) {
                if (method_exists($helper, $method)) {
                    call_user_func_array(array($helper, $method), $args);
                    return true;
                }
            }
        } else {
            echo "ERROR in " . __CLASS__ . " method `$name` not exist in helper's";
        }
    }
}
?>

Добавились две переменные $_helper и $_helperMethod в первой будут храниться наши помощники, а во второй набор методов из помощников. Так же два метода — addHelper и магический __call. С помощью первого мы добавляем helper, при этом проверяя, чтобы не появилось одинаковых методов. во втором с помощью магии мы вызываем метод из helper-ов, обходя их с помощью foreach и проверяя с помощью method_exist существование и когда находим нужный helper вызываем его метод передавая параметры с помощью call_user_function_array. В php есть call_user_method_array, который, судя по названию кажется более пригодный, но он считается устаревшим и использовать его не рекомендуется, вместо этого мы передаём в качестве параметра массив с объектом и методом.

Теперь проверим навый функционал. Изменим шаблон

1. teplater !<?=$this->escape($this->test, ENT_NOQUOTES)?>! teplater<br/>
2. !<?$this->say('what', 'say')?>!<br/>
3. !<?$this->say2('what', 'say')?>!<br/>

И теперь делегируем новые методы say и say2

class test {
    function say($say, $say2) {
        echo "test say - $say, $say2";
    }
}
class test2 {
    function say2($say, $say2) {
        echo "test2 say - $say, $say2";
    }
}
require_once('Amdy/Templater.php');
$tpl = new Amdy_Templater();
$tpl->addHelper(new test());
$tpl->addHelper(new test2());
$tpl->test = '<u style="">test</u>';
$tpl->display('test');

Создали два класса test и test2,  а у них методы say и say2. И с помощью addHelper перадаём их в шаблонизатор.

Ну и архивчег с кодом навсякий. скачать исходники

Свой шаблонизатор 2: 6 комментариев

  1. kelvin

    почему end_clean, а не ob_end_clean, ведь буферизация продолжится?
    зачем при вызове каждого метода пробегать по ним форичем, время генерации увеличим только

    1. admin Автор записи

      потому что я невнимательно читал документацию :(. спасибо, ценное замечание, исправил.
      кстати, если хочется использовать php шаблоны, то это уже скоро будет хорошей идеей. мы с коллегой бились лбом о проблему более удачного синтаксиса, особенно блочные функции как в smarty и т.д. придумали один костыль, который подойдет не только для шаблонизаторов. сейчас коллега его кодирует, идею в общих чертах я выложу, возможно, частично реализацию. способ спорный, но стал для нас золотой серединой.

      1. kelvin

        я сейчас пытаюсь шаблонизаторы изучать, потом будет mvc, тк. запутался как лучше строить архитектуру сайта.

        1. admin Автор записи

          это хорошо, что запутался, значит уже знаешь больше одного варианта. а вот с mvc всё плохо, практически нет примеров фреймворков, которые действительно соответствуют парадигме. вот те же шаблоны используют вместо полноценной функциональной вьюхи. mvc — это скорее попсятина, понимать которую следует — разделяй компоненты в соотетствии с функционалом. я использую model (ORM), view (Smarty+плагины), front controller, классы для списков и форм, компонентный подход или виджеты. в Seagull на котором я многому научился юзаются цепочки с пре и пост фильтрами, в Zend Framework появились ActinStack. Ищи как проще и удобнее для тебя.

          1. kelvin

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

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

Ваш e-mail не будет опубликован. Обязательные поля помечены *