Мини-шаблонизатор
Конечно, пользователю будет приятно, если письмо (пусть даже и сгенерированное программой) будет адресовано ему лично. Например, в поле From содержится фамилия и имя клиента, а первые строки текста звучат как-нибудь вроде: "Уважаемый ФИО!". Так что нам придется формировать текст письма "на лету"— проставлять в нем нужное имя, фамилию, тему и т. д. по общему шаблону.
В идеале такой шаблон должен ничем не отличаться от небольшого PHP-сценария с тэгами <? и ?> и возможностью использования команды echo или print, не говоря уж о всех остальных инструкциях. Но вот беда: как нам этот самый шаблон "развернуть", превратить в письмо-строку, которую потом мы будем посылать по почте? Пусть, например, у нас есть следующий шаблон письма (разделителем заголовков и тела письма служит маркер ~StartOfMail, обрабатываемый функцией PostMail()):
To: "<?=$Name?>" <<?=$email?>>
Subject: <?=$Subject?>
~StartOfMail
Дорогой <?=$Name?>!
Только что Вы подписались на наш лист рассылки.
Пожалуйста, подтвердите свое желание получать новости нашего сайта.
Если бы мы писали сценарии на PHP версии 3, задача обработки такого шаблона была бы практически невыполнимой. К счастью, при использовании PHP версии 4 все проще: в нем имеются функции "перехвата" стандартного выходного потока (о них мы уже говорили в главе 30 ).
Давайте начнем проектирование функции PostMail() с написания своеобразного "мини-шаблонизатора" — функции, которая умеет "разворачивать" шаблоны наподобие приведенного выше, возвращая окончательный текст. Назовем ее, к примеру, ExpandTemplate()
(листинг 32.1). Думаю, будет целесообразно вынести данную функцию в отдельную библиотеку, потому что она достаточно универсальна для этого.
Листинг 32.1. Функции обработки шаблонов: Minitemplate.phl
<?
// Эта функция используется для внутренних целей. Она возвращает
// "развернутый" шаблон $templ. Перед обработкой создаются переменные,
// имена которых содержатся в ключах массива $Vars, а значения — в
// соответствующих значениях массива. Если $Vars===false, то вместо
// него используется массив $GLOBALS ( то есть делаются доступными все
// глобальные переменные). Значение параметра $ReadFile "истина"
// указывает, что в $templ хранится не содержимое шаблона, а имя файла,
// из которого его можно получить.
// Замечание: параметр $Vars передается по ссылке, т. к. для
// массивов передача ссылки работает значительно быстрее, чем
// копирование.
function _RunTemplate($tmpl, $ReadFile, &$Vars)
{ // Перехватываем стандартный поток вывода
ob_start();
// Если $Vars опущен, использовать вместо него $GLOBALS. Мы
// используем ссылки для убыстрения работы, чтобы PHP не пришлось
// копировать значения, чем экономим время.
if($Vars===false) $Vars=&$GLOBALS;
// Делаем доступными коду шаблона все переменные. Также создаем
// ссылки из соображений производительности.
foreach($Vars as $k=>$v) $$k=&$Vars[$k];
// Включаем файл по include, либо же запускаем eval().
if($ReadFile) { include $tmpl; }
else eval("?>$tmpl;<?");
// Получаем содержимое буфера и закрываем его
$MTResult=ob_get_contents();
ob_end_clean();
// Возвращаем развернутый шаблон
return $MTResult;
}
// Функция "разворачивает" шаблон, тело которого расположено
// в файле $fname. Перед запуском переменные из $Vars делаются
// доступными шаблону (если этот параметр не опущен).
function ExpandFile($fname,$Vars=false)
{ return _RunTemplate($fname,true,$Vars);
}
// Функция "разворачивает" тело шаблона, явно заданное в $tmpl.
// Рекомендуется везде, где можно, применять ExpandFile() вместо
// данной функции, потому что это упрощает отладку.
function ExpandTemplate($tmpl,$Vars=false)
{ return _RunTemplate($tmpl,false,$Vars);
}
?>
Зачем нам две различных функции для "раскрытия" шаблона — ExpandTemplate() и ExpandFile()? Почему бы не использовать всегда ExpandTemplate(), предварительно загружая тело шаблона с помощью функций чтения файлов? Все дело в тонкостях обработки ошибочных ситуаций в PHP. А именно, в случае ошибки внутри файла, загружаемого по include, PHP сообщит нам имя этого файла. Если же ошибка произойдет в eval(), выведется только номер строки, что сильно затруднит отладку. Поэтому рекомендуется везде, где это допустимо, вызывать функцию ExpandFile().