Pull to refresh

Comments 86

Тоже пользовался нативными само писными шаблонизаторами, поэтому позволю себе написать критику.

первое # <?php echo $this->title ?>
такой вызов слишком долг, и не естественнен, если вы будите выводить шаблон в метоlе, то сможете в этом же методе сделать что-то вроде:
foreach $this->allVars as $k=>$v
$$k=$v;
ну фильтры для протекции навернете сами.

тогда запись будет более естественой:
<?=$title ?>

второе: я считаю что экранирование передаваемых в шаблонизатор параметров должно определяться на момент передачи параметра и по умолчанию все экранировать:
$templater->assign($varName,$value, $isProtect=true)

Это что касается критики, а в остальном статья толковая, очень правильно подмечены причины почему нужен шаблонизатор, а не «чистый» нативный подход. Для начинающих однозначно полезный материал!
1. К сожалению, не везде short_open_tag == 1, поэтому, если нужно, чтобы сайт работал на любом хостинге, приходится прибегать к длинным вызовам. И, я думаю, не нужно слишком усложнять нативный шаблонизатор, а то в конце концов получим тот же Smarty :)

2. Не думаю. Вдруг в одном месте нужно вывести экранированные данные, а в другом чистые?

В любом случае, спасибо за критику и за отзыв :)
очевидно вы не поняли что имеется в момем первом положении. Суть в том чтоб избавиться от <?php echo $this->title
Нужное выделил
а не привести к коротким тегам, это уж дело вкуса.

второе:
как правило когда нужно выводить «чистые» данные вы знаете это в моделе (практика подтверждает)
и в конце концов вы всегда можете использовать оба механизма.
Не замедлят ли фильтры работу при большом количестве переменных? Одним из моих аргументов в пользу нативных шаблонов всегда была их скорость работы.
копирование даже сотни переменных — очень быстрая операция, а про экранирование на этапе добавления, я делал так:
assign($varName,$value, $isProtect=true){
if ($isProtect)
$value = escape($value);
}

Вообще в этих вопросах скорости работает такое правило: смарти (несмотря на всю свою кривизну, громоздкость) популярен потому что на вывод шаблона тратится 2-10% времени редко больше.
Угу. Именно так и сделано у меня во фреймворке, упоминание которого в блоге так раскритиковали :)

Собственно, вот весь код рендерера: hg.balancer.ru/hgwebdir/bors-core/file/6a98c39d7867/classes/render/php.php

А вот — пример шаблона: hg.balancer.ru/hgwebdir/bors-airbase/file/30143ac88174/classes/bors/airbase/board/show/latest.tpl.php

Класс-источник данных для шаблона: hg.balancer.ru/hgwebdir/bors-airbase/file/30143ac88174/classes/bors/airbase/board/show/latest.php

И, собственно, результат работы: forums.airbase.ru/forum/latest/

(хотя фишка пока чисто экспериментальная, всё остальное — на Smarty)
Кстати!!! килограмм Кармы тому кто придумает избавиться от
ob_start();
$result = ob_get_contents();
Проблема в том, что функции перехвата потока вывода сильно медленно работают!

посмотрел ваш фраймворк и наскидку вижу что логика очень прозрачная — это плюс, но много сущностей плодите — это минус, простите если ошибся.
>Кстати!!! килограмм Кармы тому кто придумает избавиться от
ob_start();

Простого (и более быстрого, чем с перехватом) способа, ИМХО, не будет. Можно запретить в шаблоне любой вывод, сразу начинать его с "<?php..." и отказаться от всех echo в пользу $RESULT.="..."

:)

Кроме того, в моём случае можно написать обработчик не уровня рендерера тела страницы, а уровня генератора страницы в целом. Но тогда проще уже сделать шаг дальше и тупо писать отдельный PHP-скрипт, который и будет выводить всё сам (пусть и пользуясь для извлечения данных функционалом фреймворка).

>наскидку вижу что логика очень прозрачная

Это было одной из целей.

>это плюс, но много сущностей плодите

Есть такое, это обратная сторона микроядерности, модульности и многофункциональности. Компенсируется тем, что сущности все простые и унифицированные :)

Фактически, этот фреймворк — не столько набор кода, сколько набор правил и соглашений. Поэтому, когда придёт надобность, смогу быстро реализовать его на любом языке с развитой рефлексией (в ближайших планах Java, но не в области Web'а).
поподробней подалуйста что вам не нравится в механизмах рефлексии PHP!
читать если нужно напишу подробней, просто спроса не было.

>>Простого (и более быстрого, чем с перехватом) способа, ИМХО, не будет. Можно запретить в шаблоне любой вывод, сразу начинать его с "<?php..." и отказаться от всех echo в пользу $RESULT.="..."

вот что-то такое я как-то думал сделать с прекомпиляцией, но слишком много нюансов =)
>поподробней подалуйста что вам не нравится в механизмах рефлексии PHP!

Всё нравится :) Я говорю, что мою идеологию можно реализовать на любом языке с развитой рефлексией. Сейчас использую PHP. Столь же легко сделать на Python или Ruby, Perl. Немного подумав, можно и на Java :) Чем и планирую заняться как будет время и желание…

>читать если нужно напишу подробней

Пробежал пока очень бегло. У меня используется что-то среднее… Пример GET/POST обработчика actions: hg.balancer.ru/hgwebdir/bors-core/file/6a98c39d7867/inc/bors/form_save.php (19-я, 139-я строки).

Кстати, небольшой хинт: if(substr($str,0,1)=='_') работает _намного_ медленнее, чем if(preg_match('/^_/', $str)) :) Видимо, вся фишка в экономии на создании временной строки. Я когда-то Smarty так ускорил раз в 5, подменив массу substr или {} на соответствующие preg_*, особенно, когда после сравнения требовалось ещё и подстроки извлекать. Правда, было это во времена ~2.6.14. Патчи мои не приняли без комментариев, повторно уже не связывался :)
Хинт принял, проверю, учту. Спасибо
Немножко не в тему, но почему-то мне кажется, что if ($str[0]=="_") будет еще быстрее.

Кстати, проверил сейчас, оказалось, что
if(substr($str,0,1)=='_') работает 1,2 времени, if($str[0]=='_') — 0,7 времени, а if(preg_match('/^_/', $str)) — 2,1.
Да, похоже в PHP за последнее время что-то соптимизировали. У меня сейчас так вышло:

substr: time=1.13405108452
preg_match: time=1.37561202049
array: time=0.460075139999
Зато немного интереснее проверка последнего символа:

substr: time=1.58525490761
preg_match: time=1.44538593292
array: time=1.14502310753

И, наконец, цепочки символов (задача — проверить расширение файла):

substr: time=1.64853310585
preg_match: time=1.44550204277

(массив тут уже непригоден).
Проверить и получить — немного разные вещи :)

Вот сравнение самого быстрого метода со ссылки и preg_match:

test_strrchr: 3.47257399559
test_preg_match: 1.57902884483

Почувствуйте разницу :)

Если же речь идёт просто об извлечении, то:

test_strrchr: 3.38620686531
test_preg_match: 2.8885819912

Даже чтобы извлечь последние символы после ".", preg_match работает быстрее :) Кроме того, он отловит и отсутствие расширения…
А что, если strrpos($str, $ext) != strlen($str) — strlen($ext)?
Плюс можно еще offset вставить в $strrpos, с ним наверняка еще ускорить можно.
, if($str[0]=='_')

тогда уж

if($str{0}=='_')

пишите правильно, господа!
В чем разница?
Сейчас потестировал для разных типов: array, string, int; разницы в поведении не заметил.

Что я не углядел?
в том что второй вариант специально для работы с строками в 6-й версии обещали ошибку сделать на неправильные вызовы
Думаю, разница в будущем может вылезти на мультибайтовых строках.

Сейчас, правда, ни тот, ни другой формат записи с таковыми не работают.
Я конечно же могу ошибать, но вроде как история такая:
до PHP4 использовали [], потом стали рекомендовать {}, а к 6-ке собираются опять вернуться к []
Хорошо бы :)

Не вижу ничего плохого, чтобы обращаться к строке как к массиву символов!
А ini_set('short_open_tag', 'On'); перед инклудом не сработает? (дома не могу проверить)
да да проверьте и расскажите мне, очень интересно.
В документации:

short_open_tag || «1» || PHP_INI_PERDIR || PHP_INI_ALL в PHP <= 4.0.0.

Т.е. сработает в 3.xx или 4.0.0 только :)
Цитата из мануала:
short_open_tag
PHP_INI_PERDIR
PHP_INI_ALL in PHP <= 4.0.0.

PHP_INI_PERDIR 2 Entry can be set in php.ini, .htaccess or httpd.conf

Так что в .htaccess прописать использование коротких тегов — можно, но во время исполнения скрипта — нельзя :(
И я, и я пользовался нативными шаблонизаторами :) потому позвольте чуть-чуть поправить ваше предложение, вместо foreach($this->allVars as $k => $v) { $$k = $v; } есть языковая конструкция extract.
вы правы, вы абсолютно правы, мое упущение =)
Нет, по моему лучше определять экранирование в шаблоне, примерно так:

→ с экранированием: {$var}

→ без: {useHTML:$var}

Все-таки это логичнее, так как передача массива из HTML- и non-HTML данных в вашем примере невозможна.
«Магический» метод __get возвращает значение переданной в шаблон переменной. Если переменная не определена, возвращает false. В зависимости от параметров, переменные перед отдачей могут обрабатываться.


в большенстве случаев этого хватает, но на всякий случай замечу, что переменная может быть определена и иметь false, потому полезно генерировать стандартное уведомление.
а вообще функции для работы с буферами часто пишут так что они возвращают null:
fucntion __get($k){
     if (isset($this->vars[$k]))
          return $this->vars[$k];
    return null;
}
Я вот, кстати, довольно долго думал, возвращать false или null (ведь переменная можеть быть определена и иметь значение как false, так и null). Решил, что false логичнее. Вы считаете, что null логичнее?
я б null возвращал, ибо попытка вывода несуществующей переменной — колокольчик о том, что там ошибка в логике (ошибочное предположение существования), опечатка, и вы так получите нотис!
Думаю, вы правы. Спасибо, сейчас изменю.
смотрим документацию и коментарии и видим, что присвоение переменной null эквивалентно уничтожению переменной (фактически данных нет, но есть контейнер).
Может логичнее возвращать «undefined»?
Не совсем вас понял. Уточните, пожалуйста.
<?php echo $this->varName; ?>
Если переменная «varName» не установлена, то метод __get () вернет строку «undefined».
Тогда придется в конструкциях типа
<?php if (!$this->user) { ?><a href="/authorization">Авторизуйтесь</a><?php } ?>
писать
<?php if ($this->user == "undefined") { ?><a href="/authorization">Авторизуйтесь</a><?php } ?>
Мало того, что неудобно, еще и нарушает логику, имхо.
Если Вы обращаетесь в переменной user, зная, что она может быть не объявлена, это еще хуже…
нотисы генерируйте =) а не заглушки данных.
Это извращение, точнее ошибка архитектуры.
Нужно на уровне подготовки данных, «включать» и «выключать» модули (в зависимости от ролей и доступа)
Потом просто указваете… ну например <?php block('auth'); ?> или кто любит ООП <?php $interface->assign('block', 'auth');
$interface->view(); ?> и т.п. В зависимости от поступленных данных и развивается сценарий ;)
Т.е. если нет доступа юзеру, то модуль авторизации не должен включаться или выводить catch шаблона на выход.
А если переменная будет содержать такое значение? Самый правильный вариант всё таки с null.
это echo вам выведет «undefined»
думаю имеется ввиду следующее:

если воспроизвести такой код:
<?
error_reporting(E_ALL);
echo $a;
$a=null;
echo $a;
?>

то получим только один Notice, тоесть я ошибся выше и скрипт не будет вам кидать нотис на строке $this- > undefined; кидать такой нотис нужно самому используя trigger_error, определяя место вызова из debug_backtrace();

Знаете, кстати, если б вы инклюдили шаблон внутри метода, предварительно сделав:
что-то вроде:
foreach $this->allVars as $k=>$v
$$k=$v;
как я писал выше. то получили бы и нотис автоматически на строке <?php echo $title ?>
если переменная не зарегистрирована.
супер решение, можно еще так же возвращать что-то вроде «nothing», «void», а но лучше всего, мы же русские люди, возвращать строку «не установлено», ну или в крайнем случаее «ничего нет».

Здесь вам не явоскрипт, где undefined — конструкция языка такая же как и null
нет в яваскрипте никакой конструкции unefined.
Да? А вот это наверное уличная магия:
var foo;
if (foo == undefined)
{
alert(«foo is undefined»);
}

Угадай с трех раз, появица ли у нас алерт или нет.
alert( 'undefined' in window );

javascript.ru/ecmascript/part15#a-15.1
> навороченным тормозом типа Smarty.
вы делали замеры и сравнение со своим шаблонизатором? попробуйте, будет интересно.
чтоб сделать замер подобного толка нужно написать сложный шаблон для смарти и посмотреть.
из своей практики могу сказать результаты тестирования:
Простые шаблоны

генерация шаблонов:
Q AVG: 0.16 сек
S AVG: 0.18 сек
P AVG: — сек

вывод шаблонов:
Q AVG: 0.0064 сек
S AVG: 0.0065 сек
P AVG: 0.0022 сек

Сложные шаблоны

генерация шаблонов:
Q AVG: 0.19 сек
S AVG: 0.54 сек
P AVG: — сек

вывод шаблонов:
Q AVG: 0.0072 сек
S AVG: 0.0231 сек
P AVG: 0.0038 сек

AVG — среднее время на замере (1000 замеров)
Тестировалось на рабочем сервере FReeBSD Core2Duo 2.0 Ghz, думаю остальные параметры не так важны, да и не помню я их уже.

генерация шаблонов — имеется ввиду компилирование.
S- smarty
Q- Quiky
P — PHP
Да, притом не на той верстке, которая в примере, а на реальной и сложной верстке. Smarty медленней. Кроме того, нативные шаблоны более гибкие.

Но! Я не утверждаю, что Smarty… скажем так, плохой. Если кому-то удобен Smarty, и с его помощью он легко и быстро решает нужные задачи — это хорошо :) А мне вот удобнее так, кроме того, в Smarty мне действительно не хватает гибкости.
приведу пример:
Колоночная верстка, выводится 2 или 3 колонки каких-то параметров в зависимости от их количества. Провести базовые арифметические процедуры в смарти очень не удобно! а на нативном коде делаются очень просто. Эта проблема решена в квики (Quiky)
Я все же решил проверить на этом примере :)
Smarty — 0.0119
Stemp — 0.0026

Про гибкость согласен, возможностей для работы с объектами/массивами мало, вся надежда на смарти 3 :)

А код Stemp понравился, буду рекомендовать тем, кому нужен php-native шаблонизатор
Есть ещё проблема с самописными шаблонизаторами — они не подходят для коллективной разработки, поскольку в коллективной разработке нужно использовать хорошо-документированные технологии, проверенные временем, иначе при ротации сотрудников, у вас возникает проблема.
с вами не согласен в корне.
технология php проверена и обкатана, а изучить синтаксис PHP на уровне вывода переменных не труднее чем выучить синтаксис Smarty
Очень хорошо, теперь ответьте мне на вопрос:
Учавствовали ли вы в больших проектах (4-5 человек) длительное время (более года). Если да, то менялись ли у вас разработчики (хотябы 1, 2 в год). Если да, то использовали ли вы там альтернативные самописные решения. Если да, то не возникало ли ни у кого никаких проблем с этим?

Лично я учавствую в проекте более 3 лет, на 6 разработчиков, 25% из них поменялалсь 2 раза. И у нас постоянно возникают проблемы с простейшей вещью — недокументированным модулем к Drupal, который содержит альтернативный функционал модулю node.
Учавствовали ли вы в больших проектах (4-5 человек) длительное время (более года)
— да (последний оконченный, ну с которого я ушел рекламно ресселерская сеть, с нагрузкой 600хитов(просмотров) в секунду, должность тимлидер, 7 человек команда).
Если да, то менялись ли у вас разработчики (хотябы 1, 2 в год).
— да (менялось 4 человека, 3 ушло 4 пришло за год)
Если да, то использовали ли вы там альтернативные самописные решения.
— Да, уровень абстракции доступа к БД, RPC модули
Если да, то не возникало ли ни у кого никаких проблем с этим?
— Да возникли, но мы работал и в системе экстремальное программирования, в частности программирование в парах и человек который не смог разобраться оказался не коммуникабельным и ушел из проекта, остальные 3 остались.

в итоге с проекта ушли фактически все, кто занимался им в начале (остался один!, остальные с новой командой). Проект успешен, работает и расширяется.

я считаю что боязнь перед использованием собственных разработок в коллективе прежде всего из за плохой коммуникации. Улучшайте среду общения и будет лучше! что стоят 10000 строк вашей документации если у новичка есть конкретная проблема? а что если эта проблема ранее не обсуждалась? ждать нового релиза, рыться в кодах готового продукта (который часто выглядит черным ящиком)?
в целом я ЗА использование готовых решений, но иногда это не оправдано.
В частности не оправдан страх научить других людей пользоваться вашим решением, если решение работает, и нормальный код, то даже документация большинству не понадобится
Да дело в том, что боязни как раз обычно и нет, отсюда и вылазят грабли.

>> В частности не оправдан страх научить других людей пользоваться вашим решением, если решение работает, и нормальный код, то даже документация большинству не понадобится. <<
У нас в компании так не практикуется, если своё решение качует между проектами, то к нему есть документация (и не только АПИ).
все верно, видимо у вас «большая» компания, а у меня маленькая (менее 100 человек) была у нас непростительно много времени уйдет на формирование документации, проще на словах или прямо в коде описать исключения. Все эти методы привязаны к маштабу. Когда маштаб большой и нужно координировать много народу, то другие приемы работают.
у нас есть документация, но писать с помошью этого просто не реально
для коллективной разработки нужны простые решения
вот этот аргумент мне понятен больше
Поддерживаю такой подход, но сам склонился в сторону XSLT.
По поводу исключений: я считаю делать так нельзя по той причине, что отсутствие шаблона не является критической ошибкой, можно было бы просто взять за контент несуществующего шаблона пустую строку. А так, придется каждую обработку шаблона ставить в try-catch и не дай Бог это забыть…
спорно… что такое не существующий шаблон? отсутствие запланированного вывода. не поставите try-catch будет ошибка — логично.
Ну никто же не заставляет писать die(); в catch. Вы можете обработать исключение так, как вам будет нужно. Обычно, отсутствие шаблона — это все же критическая ошибка. Я в большинстве случаев при отсутствии шаблона отдаю 404.

Кроме того, в try-catch достаточно поместить метод display.
Я про то, что работа скрипта завершиться, если не поставить try-catch.
set_exception_handler не спасет от завершения работы скрипта, в отличии от error_handler'а «оттуда не возвращаются» :)
 У вас кажется опечатка в комментарии на 163 строчке. А в целом конечно хорошо — название метода XssProtection порадовало) Я бы добавил еще такую функциональность(давно крутятся просто такие идеи, а реализовывать особо не где):
 Сделать пакеты со вспомогательными функциями. Например функции для работы с датой(разные представления, рендерилки различных селекторов дат и т.д. — кстати в Smarty такое очень удобно сделано). А что бы не тащить это за собой для всех шаблонов, можно добавить параметры(аналогично xss_protection) которые будут определять нужно ли делать require_once для данного набора функции.
Да, опечатка. Спасибо, исправил.

Работу с датами решил не делать, чтобы не нагружать шаблонизатор. Я не вижу смысла делать функцию, аналогичную php-функции date();

А вообще, я буду только рад, если кто-то возьмет мой класс и доработает его для себя :) Я же старался реализовать самый минимум (на мой взгляд).
еще в 2005 году был сделан phpsavant.com, api совместимо со смарти, плагин работы с формами присутвует

<?php

// Load the Savant3 class file and create an instance.
require_once 'Savant3.php';
$tpl = new Savant3();

// Create a title.
$name = «Some Of My Favorite Books»;

// Generate an array of book authors and titles.
$booklist = array(
    array(
        'author' => 'Hernando de Soto',
        'title' => 'The Mystery of Capitalism'
    ),
    array(
        'author' => 'Neal Stephenson',
        'title' => 'Cryptonomicon'
    ),
    array(
        'author' => 'Milton Friedman',
        'title' => 'Free to Choose'
    )
);

// Assign values to the Savant instance.
$tpl->title = $name;
$tpl->books = $booklist;

// Display a template using the assigned values.
$tpl->display('books.tpl.php');
?>


<html>

    <head>
        <title><?php echo $this->eprint($this->title); ?></title>
    </head>

    <body>
        
        <?php if (is_array($this->books)): ?>
            
            <!-- A table of some books. -->
            <table>
                <tr>
                    <th>Author</th>
                    <th>Title</th>
                </tr>
                
                <?php foreach ($this->books as $key => $val): ?>
                    <tr>
                        <td><?php echo $this->eprint($val['author']); ?></td>
                        <td><?php echo $this->eprint($val['title']); ?></td>
                    </tr>
                <?php endforeach; ?>
                
            </table>
            
        <?php else: ?>
            
            <p>There are no books to display.</p>
            
        <?php endif; ?>
        
    </body>
</html>


зы от __set и __get стоит иногда отказываться, тк на производительности это может сказать хорошо
очередной велосипед с треугольными колёсами. тысячи их!
когда-нибудь тебе надоест, трахаться с «ветвлениями как таковыми» и ты проникнешься идеей «блочных шаблонизаторов».
когда-нибудь ты поймёшь, что ооп — это прежде всего объектная декомпозиция, а не запихивание всех процедурок в один класс.
когда-нибудь ты возненавидешь, спагетти и начнёшь разделять «шаблонизацию» и «логику представления».
когда-нибудь ты научишься, оптимизировать «узкие места», а не что попало.
а быть может я соврал, и ты никогда не постигнешь настоящего дао.

UFO just landed and posted this here
как один из вариантов — да. хотя, меня не покидает идея сделать подобное на чистом php, где вместо xml-деревьев, будут деревья php-массиов и объектов ;-)
Я пробовал. И насчет ее удобства я бы поспорил…
хорошо, спорить не буду, но вот чем не удобно интересно знать.
Ну, хотя бы тем, что там нет даже нормального if.
Шо опять?
Когда-то писал шабонизатор еще для PHP4 (http://code.google.com/p/hstemplate/)
В результате что-либо лучше нежели Zend_View нарисовать врядли получиться (его замечательно юзать без самого фреймворка можно)
>В результате что-либо лучше нежели Zend_View нарисовать врядли получиться

Синтаксис избыточный. Там, где в Zend_View нужно писать:

<?php echo $this->escape($val['author']) ?>

в том же Smarty пишется:

{$author|escape}
Синтаксис взорвал мозг. Избыточность невероятная (и ничем не обоснованная).
Sign up to leave a comment.

Articles