Недавно в блоге PHP пролетала статья про наследование шаблонов в Smarty, которая навела меня на мысль: я уже на протяжении нескольких лет использую Smarty в качестве основного шаблонизатора и у меня накопилось множество написанных мною плагинов для расширения базового функционала. Почему бы не поделиться с сообществом своими наработками и послушать других? Я думаю, у многих есть чем поделиться по этой теме…
Стандартная архитектура Model-View-Controller (MVC) подразумевает разделение логики, данных и внешнего вида. В теории всё красиво. А на практике как быть, скажем, с датой рождения, которая в базе данных лежит в виде «1980-01-19», а пользователю надо вывести «19 января 1980»? Кто этим должен заниматься? Вот тут у многих известных мне разработчиков и начинает ломаться логика. Это самое преобразование внешнего вида куда только не всовывают (да-да, доводилось встречать и уникумов, хранящих это в БД в виде текстовой строки).
Думаю понятно из названия статьи, что я считаю правильным нагружать шаблон этими преобразованиями: пусть отображение само решает, в каком виде и как ему выводить данные.
Итак, приступим…
Казалось бы — есть стандартный модификатор date_format, что ещё нужно? А вот такой я зануда, и мне его мало:
Внешний вид: {$variable|date:'формат'}. В качестве необязательного формата используется 'date', 'datetime' либо 'time'. Все описанные ранее проблемы модификатор решает сам.
Файл smarty/plugins/modifier.date.php (скачать)
Обращаю внимание на волшебную глобальную переменную $Language, которая хранит форматы вывода дат для разных языков и нормальные названия месяцев на родном посетителю языке.
Т.е. для английского языка:
А для русского:
Содержимое $Language['months'] оставлю для самостоятельного сочинения ;)
В некоторых проектах на этот модификатор дополнительно ложится задача пересчёта часовой зоны, в зависимости от настроек пользователя. Пустячок, а приятно — не надо делать резких телодвижений для этого в коде модулей.
Полностью аналогичен предыдущему случаю. Разница лишь во входящих данных. Этот ожидает на входе не UNIX_TIMESTAMP, а MySQL'ные DATETIME или DATE.
Файл smarty/plugins/modifier.date_sql.php (скачать)
UPDATE: У ряда комментаторов возник вопрос: почему я вместо strtotime() использую жуткую комбинацию strtr() и регулярных выражений? Ответ простой: для корректной обработки неполных дат вида «1980-00-00» (все знают мой возраст, но не день роджения) или «0000-01-19» (все поздравляют меня с днём рождения, но никто не знает, сколько мне лет).
Тут возникает закономерный вопрос: нафига вообще 2 модификатора? Пусть будет один, который внутри себя разбирается во входных данных. Я считаю это интересным решением, которое я пока не могу реализовать по ряду технических особенностей фреймворка (а может это просто лень?).
UPDATE: Ряд толковых комментариев сподвиг меня склеить и упростить эти оба плагина, как я и собирался, но по куче причин не решался. Получилось вот что:
Файл smarty/plugins/modifier.date.php (скачать)
Благодарю за помощь.
На этом моменте хочется прерваться, хотя есть ещё пяток интересных плагинов. Поскольку времени на написание я потратил изрядно (чукча не писатель), а интерес сообщества к этой теме мне неизвестен: полетят ли в меня тухлые помидоры или статья просто затеряется невостребованной — я не знаю.
UPDATE: Уже написана и выложена вторая часть.
P.S. Сильно не ругайте, ибо ППНХ ;)
0. Disclaimer
- Эта статья написана не для того, чтобы убедить/разубедить в использовании Smarty. Используете — может быть найдёте здесь что-то интересное, нет — воздержитесь, пожалуйста, от холиваров. Я прекрасно и без вас знаю, какой гадкий, мерзкий и тормознутый Smarty по сравнению с [сюда вставить название вашего любимого шаблонизатора]. Статья не о том.
- Плагины создавались на протяжении нескольких лет, поэтому стиль несколько разношёрстный, в некоторых ситуациях они не очень дружат друг с другом. Я делюсь идеями, которые весьма просты в реализации, поэтому придираться к моей реализации не стоит. Тем не менее, за разумную критику буду благодарен.
- Плагины зачастую крутятся в составе самопального микро-фреймворка, который поддерживает многоязычность. В приведённых исходниках я заменил все вызовы функций фреймворка на переменные, поэтому код моментами выглядит дебильно из-за переменных, global и прочих корректив.
1. Модификаторы (modifiers)
Стандартная архитектура Model-View-Controller (MVC) подразумевает разделение логики, данных и внешнего вида. В теории всё красиво. А на практике как быть, скажем, с датой рождения, которая в базе данных лежит в виде «1980-01-19», а пользователю надо вывести «19 января 1980»? Кто этим должен заниматься? Вот тут у многих известных мне разработчиков и начинает ломаться логика. Это самое преобразование внешнего вида куда только не всовывают (да-да, доводилось встречать и уникумов, хранящих это в БД в виде текстовой строки).
Думаю понятно из названия статьи, что я считаю правильным нагружать шаблон этими преобразованиями: пусть отображение само решает, в каком виде и как ему выводить данные.
Итак, приступим…
1.1. Вывод дат
Казалось бы — есть стандартный модификатор date_format, что ещё нужно? А вот такой я зануда, и мне его мало:
- Во-первых, он непригоден для уже описанного ранее случая с днём рождения, хранящегося в виде date или datetime в БД.
- Во-вторых, я не должен в каждом месте набивать всякие иероглифы вида: {$yesterday|date_format:'%A, %B %e, %Y'} (пример из справки).
- И, наконец, в-третьих, большинство разрабатываемых мною сайтов — многоязычные. Т.е. для русского языка я должен использовать формат '%d %B %Y', для английского '%B %d, %Y' и т.д.
1.1.1. Модификатор date
Внешний вид: {$variable|date:'формат'}. В качестве необязательного формата используется 'date', 'datetime' либо 'time'. Все описанные ранее проблемы модификатор решает сам.
Файл smarty/plugins/modifier.date.php (скачать)
/**
* Модификатор date: unix_timestamp => дата
*
* param string $string
* param string $format
* return string
*/
function smarty_modifier_date($string, $format='datetime')
{
global $Language;
if(!is_numeric($string))
{
trigger_error('Modifier date: invalid input data!');
}
if ($format == 'date')
{
$format = $Language['global']['date_format'];
}
elseif ($format == 'datetime')
{
$format = $Language['global']['datetime_format'];
}
elseif ($format == 'time')
{
$format = $Language['global']['time_format'];
}
else
// Клинический случай
trigger_error('Modifier date: invalid format specified!');
$format = str_replace('%B', $Language['months'][date('n', $string)], $format);
return strftime($format, $string);
}
Обращаю внимание на волшебную глобальную переменную $Language, которая хранит форматы вывода дат для разных языков и нормальные названия месяцев на родном посетителю языке.
Т.е. для английского языка:
$Language['global']['date_format'] = '%B %d, %Y';
$Language['global']['time_format'] = '%H:%M';
$Language['global']['datetime_format'] = '%B %d, %Y %H:%M';
А для русского:
$Language['global']['date_format'] = '%d %B %Y';
$Language['global']['time_format'] = '%H:%M';
$Language['global']['datetime_format'] = '%d %B %Y %H:%M';
Содержимое $Language['months'] оставлю для самостоятельного сочинения ;)
В некоторых проектах на этот модификатор дополнительно ложится задача пересчёта часовой зоны, в зависимости от настроек пользователя. Пустячок, а приятно — не надо делать резких телодвижений для этого в коде модулей.
1.1.2. Модификатор date_sql
Полностью аналогичен предыдущему случаю. Разница лишь во входящих данных. Этот ожидает на входе не UNIX_TIMESTAMP, а MySQL'ные DATETIME или DATE.
Файл smarty/plugins/modifier.date_sql.php (скачать)
/**
* Модификатор date_sql: Преобразование sql date/datetime в дату
*
* param string $string
* param string $format
* return string
*/
function smarty_modifier_date_sql($string, $format='datetime')
{
global $Language;
// Это date?
if (!preg_match('/^(\d{4})-(\d{1,2})-(\d{1,2})$/', $string, $result))
// Это datetime?
if (!preg_match('/^(\d{4})-(\d{1,2})-(\d{1,2}) (\d{1,2}):(\d{1,2}):(\d{1,2})$/', $string, $result))
// Я понял! Это невесть что!
trigger_error('Invalid data for date_sql modifier!');
$replace = array(
'%d' => $result[3],
'%B' => $Language['months'][(int)$result[2]],
'%Y' => $result[1],
'%H' => $result[4],
'%M' => $result[5],
'%S' => $result[6],
);
if ($format == 'date')
{
$format = $Language['global']['date_format'];
}
elseif ($format == 'datetime')
{
$format = $Language['global']['datetime_format'];
}
elseif ($format == 'time')
{
$format = $Language['global']['time_format'];
}
else
// Клинический случай
trigger_error('Invalid date format for date_sql modifier!');
return strtr($format, $replace);
}
UPDATE: У ряда комментаторов возник вопрос: почему я вместо strtotime() использую жуткую комбинацию strtr() и регулярных выражений? Ответ простой: для корректной обработки неполных дат вида «1980-00-00» (все знают мой возраст, но не день роджения) или «0000-01-19» (все поздравляют меня с днём рождения, но никто не знает, сколько мне лет).
Тут возникает закономерный вопрос: нафига вообще 2 модификатора? Пусть будет один, который внутри себя разбирается во входных данных. Я считаю это интересным решением, которое я пока не могу реализовать по ряду технических особенностей фреймворка (а может это просто лень?).
UPDATE: Ряд толковых комментариев сподвиг меня склеить и упростить эти оба плагина, как я и собирался, но по куче причин не решался. Получилось вот что:
Файл smarty/plugins/modifier.date.php (скачать)
<?php
/**
* Модификатор date: unix_timestamp, date, datetime => дата на человеческом языке
*
* param string $string
* param string $format — 'date', 'time', 'datetime'
* return string
*/
function smarty_modifier_date($string, $format='datetime')
{
global $Language;
if (!isset($Language['global'][$format.'_format']))
{
trigger_error('Modifier date: invalid format specified!');
return '';
}
$format = $Language['global'][$format.'_format'];
if (!is_numeric($string))
{
// Попытаемся прочтитать date и datetime
$timestamp = strtotime($string);
if ($timestamp!==FALSE && $timestamp!=-1 /*PHP<5.1*/)
{
$string = $timestamp;
}
}
// Это похоже на unix_timestamp?
if (is_numeric($string))
{
// Месяц на человеческом языке
$format = str_replace('%B', $Language['Month'][date('n', $string)], $format);
return strftime($format, $string);
}
// Мы ещё здесь? Что-то неладно...
trigger_error('Invalid data for date modifier!');
return '';
}
?>
Благодарю за помощь.
На этом моменте хочется прерваться, хотя есть ещё пяток интересных плагинов. Поскольку времени на написание я потратил изрядно (чукча не писатель), а интерес сообщества к этой теме мне неизвестен: полетят ли в меня тухлые помидоры или статья просто затеряется невостребованной — я не знаю.
UPDATE: Уже написана и выложена вторая часть.
P.S. Сильно не ругайте, ибо ППНХ ;)