Шаблонизация

26 Январь 2008 20 Comments Иван Блинков

Наверняка Вы часто замечали, что в пределах одного сайта все (или покрайней мере большинство) страниц имеют много общего: структуру, расположение элементов, дизайн и так далее. Основным различием обычно является лишь содержание. Естественно, что делается это не спроста: именно общие компоненты сайта создают в голове посетителей тот самый образ, который производит общее подсознательное впечатление о сайте, а также позволяет посетителям отличать сайт А от сайта Б.

Продолжая разговор, начатый еще в одной из предыдущих статей, рассмотрим организацию интерфейса между двумя другими составляющими практически любого интернет-проекта: скриптов (все так же на примере PHP) и страницами, отправляемыми посетителям посредством http-сервера.

С точки зрения веб-разработчика было бы как минимум не логично мешать в кучу постоянные части страниц с динамическими. Для этого существует множество причин, в том числе, например, экономия вычислительной мощности сервера на отсутствии необходимости каждый раз заново генерировать статичные элементы или неминуемое сокращение объемов кода, который необходимо написать, в случае если статический и динамический контент разделены. Отделенную подобным образом статическую часть страниц (слегка модифицированную с целью обозначить правила, по которым будет проводиться "заполнение" ее динамическим контентом) принято называть словом "шаблон".

Наверняка у Вас уже возникло два вполне логичных вопроса:

  1. Как можно разделить таким образом контент?
  2. Как потом восстановить страницу в исходном виде?

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

Какие-либо цифры оценки производительности приводить не буду, так как, во-первых, в Сети можно найти много benchmark’ов, посвященных этой теме, а, во-вторых, просто-напросто вовсе не о цифрах я хотел с Вами поговорить. Как известно самым производительным по крайней мере с теоретической точки зрения является метод под названием $php$ mess, заключается он в следующем: вся страница размещается в рамках одного файла, при этом статическая часть документа пишется просто "как есть" в соответствии с необходимым стандартом, а изменяемые части организуются размещенным в необходимых местах PHP-кодом, окруженным стандартной конструкцией <?php   ?>. Но огромнейший недостаток очевиден — огромное количество информации расположенной в одном файле, при отсутствии какого-либо более четкого разделения PHP-кода и остального содержимого, чем указанная выше конструкция, приводит к постоянной путанице в коде, а также существенным затратам времени программиста при попытках исправить ту или иную часть документа.

На противоположной стороне нашей шкалы удобство-производительность я бы расположил уже упомянутое выше решение под названием Smarty. Представляет оно собой целую систему, реализованную также на PHP, и предоставляющую огромное количество возможностей по решению нашей задачи. Шаблоны хранятся в отдельных файлах, для определения мест расположения динамического контента используется специальный синтаксис, который прост как три копейки, так как разрабатывался с расчетом не на программистов, а по принципу "чем проще, тем лучше". Именно этот факт сделал Smarty одним из самых (если не самым) распространенных движков шаблонизации (или как их принято правильно называть "Template Engine"). Но, к сожалению, за удобство приходится платить, в этом случае производительностью: вся система сама по себе громоздка и состоит из множества файлов, между которыми все данные так или иначе передаются, а так как написано она на PHP (который является далеко не самым производительным языком программирования, в основном в силу своей интерпритируемости и некоторых других особенностей), конкуренции в плане производительности многим другим вариантам решения нашей задачи Smarty составить не в состоянии.

Одним из лучших "компромиссных" вариантов, которые доступны на данный момент, могу назвать также упомянутый выше Blitz. Реализован он в виде модуля PHP, написанного на языке C, что является залогом его отличной производительности. При этом общая его концепция близка к Smarty: шаблоны также хранятся в отдельных файлах и подчинены незамысловатому синтаксису (который вообще можно понять и запомнить буквально за 15-20 минут, прочитав статью, ссылку на которую я уже приводил выше), а в PHP-скриптах после установки становится доступен специальный класс для управления модулем. Но основное достоинство этого решения является одновременно и его основным недостатком — редкий хостинг имеет этот модуль в списке предустановленных (видимо в силу своей не очень обширной известности, обусловленной ), а доступ к http-серверу и PHP-интерпретатору, который необходим для установки PHP-модулей, предоставляется чаще всего только на дорогих тарифах виртуального хостинга или на различных вариантах VPS или арендуемых серверов.

Помимо этого некоторые энтузиасты берутся на написание "собственных" Template Engine, базирующихся на различных вариантов использования PHP-функций вроде preg_replace. Если честно такие попытки редко заканчиваются успехом: в лучшем случае удается добиться удобства использования самим разработчиком, но чаще всего в ущерб производительности. Заниматься подобными экспериментами я Вам не советую, вместо этого я предлагаю написать "обертку" к приглянувшемуся распространенному Template Engine, что позволит не только сделать его использование более удобным конкретно для Вас, но и позволит заменить его на другой с минимальными затратами сил и времени (например в случае, если модуль Blitz недоступен).

Разрабатываем "обертку"

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

Если Ваш выбор всетаки пал на написание "оболочки", не смотря на принесение в жертву несущественной части производительности, то стоит для начала определиться: а что же мы будем "заворачивать"? В качестве примера я, пожалуй, буду использовать Blitz, как самый оптимальный вариант (по крайней мере с моей точки зрения).

Начать стоит как обычно с пустой заготовки для класса:

engine=new Blitz('./template/'.$template.'.tpl');
  }
}
?>

Далее следует решить какие все же модификации мы будем производить для собственного удобства над стандартным решением. Попробую привести несколько примеров в отношении Blitz, для начала хочу обратить внимание, что при внимательном прочтении все той же статьи от разработчика этого шаблонизатора, можно обнаружить, что модуль показывает более высокие показатели производительности при однократном вызове метода set. Достичь это можно выполнением этого метода с указанием в качестве одного из входных параметров "многоуровнего" массива, составленного специальным образом (надеюсь Вы все же к этому моменту уже успели прочитать неоднократно упоминавшуюся статью, и представляете принцип работы модуля). Написание механизма составления такого массива позволит как сократить время разработки, так и сэкономит драгоценные миллисекунды, вычитаемые из свободного времени посетителей сайта в процессе генерации страницы.

В любом случае понадобится переменная для его хранения:

array=array();
    //можно сразу указать указать путь к папке с шаблонами
    $this->engine=new Blitz('./template/'.$template.'.tpl');
  }
}
?>

А также метод, переопределяющий стандартный set на метод, добавляющий новые значения к нашему массиву (хотя можно и любое другое понравившееся название использовать):

function set($caption,$value)
{
  $this->array[$caption]=$value;
}

После чего оригинальный set можно использовать уже непосредственно перед parse, с указанием уже собранного массива в качестве параметра. За компанию позволю произвести себе еще одну модификацию: в подавляющем большинстве случаев parse используется в совокупности с echo, чтобы не указывать каждый раз это слово — можно включить его прямо в наш класс:

function parse()
{
  if(count($this->array))$this->engine->set($this->array);
  echo $this->engine->parse();
}

Еще одним возможным вариантом модификации может стать обработка всех (или какой-то части, если есть необходимость) динамических данных с помощью какой-либо функции, например это актуально для htmlspecialchars:

function set($caption,$value)
{
  $this->array[$caption]=$this->html($value);
}
function rawset($caption,$value)
{
  $this->array[$caption]=$value;
}
private function html($array)
{
  if(is_array($array))
  {
    foreach($array as $caption => $value)
    $value=$this->html($value);
    return $array;
  }
  else return htmlspecialchars($array,ENT_QUOTES);
}

Как нетрудно заметить, в методе используется рекурсия, так как структура передаваемых параметром массивов неизвестна.

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

20 comments

  • Спасибо, про Blitz не знал, посмотрим, погоняем. Но вот что меня раздражает во всех этих шаблонизаторах — так это набоность учить этот новый язык шаблонизации. PHP — сам по себе и является языком шаблонизации, так он был задуман с самого начала, по-крайней мере. Поэтому рекомендую посмотреть в сторону http://www.kwasd.nl/expose/ , по первых быстрый, во вторых копия синтаксиса PHP, а если чего нет — допиши планин сам.

  • Спорный достаточно вопрос, специально учить синтаксис для каждого шаблонизатора, с которым приход приходится иметь дело, по-моему является абсолютно бессмысленным занятием. Если пользуешься им редко — достаточно каждый раз когда с ним сталкиваешься просматривать по диагонали manual, а при постоянном использовании даже этого делать не приходится — простенький синтаксис любого Template Engine запоминается при частом использовании в кратчайшие сроки.
    В какой-то степени это можно и к языкам программирования отнести, но с ними не все так просто, как с шаблонами.

  • PHP — сам по себе и является языком шаблонизации

    Именно поэтому, писать надо не на разросшемся до неприличия шаблонном движке, а на нормальном языке под нормальным фреймворком. По поводу фреймворка, почему-то вряд ли кто удивится, если создание программы типа текствый редактор под винды будет не на чистом WinApi, а с использованием Borland VCL или Microsoft .NET Windows Forms, но с веб-приложениями всё иначе — селект из базы в шаблонер, а результат юзеру это нормально.

    Гуглить Ruby on Rails, Django, Pylons, Symfony (для фанатов PHP).

  • [quote comment="60"]Именно поэтому, писать надо не на разросшемся до неприличия шаблонном движке, а на нормальном языке под нормальным фреймворком.[/quote]
    Слово надо в этом предложении все же неуместно, каждый сам решает что и как ему писать, и писать ли вообще. Да, существует множество альтернативных вариантов написания приложений, указанные Вами framework’и — среди них. У каждого варианта есть множество преимуществ и недостатков, которые можно обсуждать до посинения, но моей цель при написании этой записи было лишь предложить свой подход к решению этой ситуации, но никто не может Вас заставить пользоваться именно этим вариантом, ровно как и каким-либо другим.

  • Оно неуместно только в суде, для политкорректности, да. Можете прочитать его иначе и отбросить защиту свободы выбора, на которую посягать с моей стороны было бы просто глупо, потому что я не властен. Например, «стоит взглянуть на фреймворки». Если б я хотел заставлять, то уделил бы внимание слову «надо», а не теме комментария. Идея комментария — всё уже написано. Осталось только найти и использовать.

  • [quote comment="128"]Идея комментария — всё уже написано. Осталось только найти и использовать.[/quote]А как же прогресс? Если бы все следовали этому принципу — программисты бы вымерли как класс, и кодеры вместе с ними :)

  • Кашевар использует воду и крупу. Посетитель столовой использует кашу. У них просто разные задачи.
    Если у программиста задача — написать движок блога, и он изобретает движок шаблонов — он идиот. А если он использует движок шаблонов и пишет блог — он программист. Может быть блог будет жуть какой прогрессивный, с кучей полезных фич, но изобретать велосипед это не программирование.

    Мне очень жаль, что приходится описывать и этот случай тоже, но видимо, нужно. Я не говорю, что придумывать шаблонный движок это плохо. Если задача такая, то это замечательно. Если существующие решения не подходят — это вынужденная необходимость, тоже замечательно. Если человек хочет поучиться, то о нем вообще речь не идет — тут любые задачи и подходы годятся.

  • [quote comment="146"]Мне очень жаль, что приходится описывать и этот случай тоже, но видимо, нужно. [/quote]
    Нужно для чего?

    [quote comment="146"]Если у программиста задача — написать движок блога, и он изобретает движок шаблонов — он идиот. А если он использует движок шаблонов и пишет блог — он программист. Может быть блог будет жуть какой прогрессивный, с кучей полезных фич, но изобретать велосипед это не программирование.[/quote]
    Более чем спорно. В процессе чтения этих Ваших примеров у меня в голове упорно возникает образ классического ленивого программиста, для которого главное как можно быстрее получить свою денежку с минимумом телодвижений со своей стороны.

    Если для качественного решения поставленной задачи (блог) по каким-либо причинам требуется особым образом работающий дополнительный программный продукт (движок шаблонов), то если он его напишет по-моему его можно будет считать намного болше программистом, чем того, кто не смотря на не полное соответствием поставленным требованием воспользовался бы чужим трудом. Да и вообще на практике в таких ситуациях (по крайней мере лично мне) бывает проще самому с нуля разобраться в алгоритме реализации того или иного функционала, чем копаться в чужом коде в поисках «а где же вон в той функции можно поменять значение вон той переменной» — особенно эт актуально когда от CMS нужен какой-либо нестандартной функционал, но и в исходниках Smarty какого-нибудь копаться, наверное, занятие не очень приятное.

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

    /* Не очень уверен, что у меня получилось адекватно выразить свои мысли, может быть через какое-то время, когда буду менее уставшим, попытаюсь более вразумительно это все перефразировать. */

  • Анонимный посетитель:

    Нужно для чего?

    Видимо не нужно, потому что даже расписав всё по полочкам, не смог объяснить свою точку зрения.

    Еще раз повторюсь. Если задача такая, то это замечательно. Если существующие решения не подходят — это вынужденная необходимость, тоже замечательно.
    Этот участок как раз обращается к оспариваемому вами случаю с нестандартным функционалом.

    А ленивый программист не будет писать вообще. Он возьмет Delphi, накидает компонентов и сдаст заказчику. Так сделан наш национальный школьный портал. Я тоже против “быстро взять бабло и свалить”.

  • Просто все упирается цель, которую Вы перед собой поставили: заставить меня согласиться с Вами или просто высказаться? Для первого я не вижу весомой аргументации, а второе Вам уже удалось неоднократно.

  • Чисто ознакомительнфй пост. Новое тольео то что BLitz на самом деле модуль для PHP, да ещё редко встречающйся А я уже хотел пробовать.

    P.S Я что это за плагин распознающий страну посетителя?

  • [quote comment="190"]Новое тольео то что BLitz на самом деле модуль для PHP, да ещё редко встречающйся А я уже хотел пробовать.[/quote]Если есть такая возможность, то попробовать в любом случае имеет смысл — он того стоит.
    [quote comment="190"]что это за плагин распознающий страну посетителя?[/quote]ip2nation

  • Костя:

    А чем лучше или хуже вариант, когда вместо шаблонизации используется файл для расчёта данных, в конце которого просто стоит include файла со статикой? Ну а в статике в нужных местах просто вставлять переменные…

  • [quote comment="390"]А чем лучше или хуже вариант, когда вместо шаблонизации используется файл для расчёта данных, в конце которого просто стоит include файла со статикой? Ну а в статике в нужных местах просто вставлять переменные…[/quote]Смотря с какой точки зрения взгляд Вас интересует.

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

    Есть вариант писать php-код и разметку вместе в одном файле и это будет существенно более производительно, но с точки зрения программиста — это просто ужас, так как путаться в собственном коде начинаешь заметно быстрее, когда он «разбавлен» (X)HTML.

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

    P.S.: Жалко, что Вы не удосужились представиться…

  • Костя:

    > Смотря с какой точки зрения взгляд Вас интересует.

    С точки зрения удобства разработки и скорости работы результата. Вернее, вопрос стоит поставить немного по-другому: «Чем шаблонизация лучше/хуже инклудов?»

    > Но include не относится к числу самых быстро выполняемых функций в PHP, что не самым лучшим образом сказывается на его производительности.

    Хм, не знал. Неужели все эти навороты с шаблонами будут работать быстрее, чем просто include? За счет чего это происходит? — ведь шаблон точно так же надо прочитать с диска, но вдобавок его надо ещё и интерпретировать и только потом отдать в php…

    > Есть вариант писать PHP-код и разметку вместе в одном файле и это будет существенно более производительно, но с точки зрения программиста — это просто ужас, так как путаться в собственном коде начинаешь заметно быстрее, когда он “разбавлен” (X)HTML.

    Ну это и так понятно. Хотя есть ещё вариант — вначале файла обработка данных, а в конце сам html со вставкой переменных :) Но этот вариант не рассматриваем — его преимущества и недостатки ясны без пояснений.
    Хотя… может кто-то может подсказать редактор, который упрощает работу при таком подходе?

  • [quote comment="395"]С точки зрения удобства разработки и скорости работы результата. Вернее, вопрос стоит поставить немного по-другому: «Чем шаблонизация лучше/хуже инклудов?»[/quote]С точки зрения удобства разработки дело конечно субъективное, но когда дело доходит до условий и итераций частей шаблона (или HTML-файла с PHP-переменными) банальной вставкой переменных дело не ограничится — начнется интеграция PHP в HTML — не самое приятное занятие и для понимания не идеально. В любом указанном в посте средстве шаблонизации эти операции выполняются проще и удобнее, но требуют прочтения коротенькой инструкции для осознания принципов их работы.

    Производительность зависит от многих параметров, цифры можно посмотреть на многократно упоминавшемся в посте сайте Алексаея Рыбака, да и чисто визульно порой разницу между различными способами генерации HTML можно заметить при желании. Как сбалансированный вариант между удобством и более чем достаточной производительностью мне более по душе Blitz.
    [quote comment="395"]Неужели все эти навороты с шаблонами будут работать быстрее, чем просто include? За счет чего это происходит? — ведь шаблон точно так же надо прочитать с диска, но вдобавок его надо ещё и интерпретировать и только потом отдать в php…[/quote]В том-то и дело, что они не интерпретируются, а компилируются (вроде) и отдаются не обратно в PHP, а напрямую http-серверу, а производится это все не относительно медленным интерпретируемым PHP, а существенно более производительным скомпилированным extension’ом на С (если говорить о Blitz).[quote comment="395"]Хотя… может кто-то может подсказать редактор, который упрощает работу при таком подходе?[/quote]Сомневаюсь, что такой существует, но если все же кто-то сможет привести ссылку — было бы интересно посмотреть.

  • Костя:

    Про удобство всё понял, спасибо. А производительность, как я посмотрел в таблице, у php-includes всё-же чуть больше, чем у blitz-incudes (если включить акселерацию).

    И ещё, как я понял, если использовать fastcgi, то самым быстрым окажется чистый php, т.к. кол-во includes при этом не будет играть роли. Я правильно догадываюсь?

  • [quote comment="399"]Про удобство всё понял, спасибо. А производительность, как я посмотрел в таблице, у php-includes всё-же чуть больше, чем у blitz-incudes (если включить акселерацию).

    И ещё, как я понял, если использовать fastcgi, то самым быстрым окажется чистый php, т.к. кол-во includes при этом не будет играть роли. Я правильно догадываюсь?[/quote]Производительность зависит от очень длинного списка факторов, начиная от сложности шаблона и заканчивая особенностями оборудования и операционной системы. Доля ошибки в такого рода синтетических тестах достаточно велика. Какие-либо выводы из формулировок «чуть больше» не стоит.

    Используя lighty + mod_fastcgi в одном из проектов мне доводилось наблюдать заметный невооруженным глазом прирост производительности при переходе с include’ов на Blitz, возможно это было просто совпадение — не знаю, но синтетические тесты мое субъективное восприятие впоследствии подтвердили (на двух машинах ради интереса сравнивал).

    По-моему эта моя заметка — как минимум повод задуматься о данном вопросе, а что уж Вы решите использовать на практике (если она у Вас имеется) — решать только Вам.

  • zubar:

    Max Replace — Мой PHP шаблонизатор:)

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

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

*

Можно использовать следующие HTML-теги и атрибуты: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>