Kuzma's PHP Look

Home page: http://kuzma.russofile.ru
Автор: Феськов Кузьма (kuzma@russofile.ru, http://kuzma.russofile.ru)

Smarty – не просто шаблоны

Содержание

Вступление
Компонентная модель
Общие классы
Функция «Компонент»
Компонент
Обмен данными между компонентами
Итоги

Вступление

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

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

Компонентная модель

Одна из сильных сторон Smarty – это возможность создавать свои плагины и расширения. Воспользуемся этой его возможностью в полной мере и попробуем реализовать на ее основе компонентную модель сайта.

Что такое в нашем понимании компонент? Под компонентом я буду подразумевать обособленный блок сайта, который умеет выполнять строго определенный набор действий и, в результате своей работы, либо выводить что-либо на экран, либо готовить какие-то данные для других компонентов.

Компонент – это элемент сайта, который, во-первых, точно знает какие действия он может совершать, во-вторых, какие библиотеки для совершения этих действий ему нужны.

В результате сайт будет состоять из ядра - набора библиотек (классов), которые содержат в себе какие-то низкоуровневые действия, например работают с базой, – и компоненты, которые подключают эти библиотеки и, в нужном порядке, вызывают те или иные их методы.

В чем положительная сторона такого подхода?

  1. вы пишите специальные классы (объекты) ядра своего сайта, они никак не зависят от вывода на экран, а просто подготавливают и обрабатывают данные, тем самым обеспечивается высокая переносимость кода и возможность его повторного использования;
  2. в случае командной работы, вам не надо заботиться о том, что делает соседний программист, который пишет свой компонент;
  3. стандартизация кода – вы можете создать интерфейс компонента и строго следовать его требованиям, что позволит одному программисту легко разобраться в компоненте другого программиста.

Это основные преимущества данного подхода.

Общие классы

Безусловно, в каждом приложении есть набор классов, которые нужны всем, или большинству компонентов, например, класс для работы с базой. Как поступить в таком случае? И для этой проблемы в Smarty есть удачное решение. Впрочем, давайте по порядку.

Сначала инициализируем класс Smarty:

<?php
/** Путь до Smarty */
define('SMARTY_DIR', '/smarty/');

/** Путь до Smarty шаблонов */
define('TEMPLATES', '/templates/');

/** Путь до Smarty компилированных шаблонов */
define('TEMPLATES_C', '/templates_c/');

require_once(
SMARTY_DIR . 'Smarty.class.php');
$smarty = new Smarty;
$smarty->compile_check = TRUE;
$smarty->force_compile = TRUE;
$smarty->template_dir = TEMPLATES;
$smarty->compile_dir = TEMPLATES_C;
$smarty->plugins_dir[] = LIBS_PATH;
$smarty->caching = FALSE;
?>

Итак, Smarty готов к работе. Что необходимо сделать с общими классами, чтобы обеспечить доступ к ним внутри любого компонента?

Скажем, у нас есть несколько общих компонентов:

  1. adodb – класс для работы с базой;
  2. session – класс для работы с сессиями;
  3. errors – класс для обработки ошибок;
  4. variables – класс для хранения и обработки состояния массивов $_GET и $_POST.

Первым делом, нам необходимо создать объекты данных классов (параметры конструкторов зависят от вашей конкретной реализации). В целом, это делается стандартным образом:

<?php
require_once(CLASSES_DIR . '/variables.class.php');
$vars = new Variables();

require_once(
CLASSES_DIR . '/errors.class.php');
$errors = new Errors($smarty);

require_once(
CLASSES_DIR . '/security.class.php');
$security = new Security($adodb);

// и так далее...
?>

Объекты созданы и теперь необходимо обеспечить доступ к ним в любом компоненте нашего приложения. Для этого необходимо зарегистрировать данные объекты в Smarty:

<?php
$smarty
->register_object('adodb', $adodb);

$smarty->register_object('vars', $vars);

$smarty->register_object('errors', $errors);

$smarty->register_object('security', $security);
?>

Для регистрации объектов мы используем метод Smarty register_object(). После чего ваш объект станет доступным везде, где он может понадобиться. Как «достать» нужный вам зарегистрированный объект мы рассмотрим в следующей части.

Функция «Компонент»

К текущему моменту Smarty ничего не знает о каких-либо компонентах, и нам нам надо научить его работать с ними. Для этого мы добавляем к синтаксису шаблонов новый тег – {component}. Чтобы Smarty научился обрабатывать этот тег – пишем простую функцию:

<?php
// Smarty function Component
//
// @author Feskov Kuzma

function smarty_function_component($params, &$smarty) {
    
$adodb = &$smarty->get_registered_object('adodb');
    
$vars = &$smarty->get_registered_object('vars');
    
$errors = &$smarty->get_registered_object('errors');
    
$security = &$smarty->get_registered_object('security');

    if (empty(
$params['name'])) {
        
$params['name'] = 'site_view';
    }
    if (
is_file(ADMIN_LIBS_PATH . '/' . $params['name'] . '.component.php')) {
        require(
ADMIN_LIBS_PATH . '/' . $params['name'] . '.component.php');
    } else {
        echo
'Component <strong>' . $params['name'] . '</strong> not found';
    }
    unset(
$adodb, $errors, $security, $vars);
}
?>

Давайте разберемся, что делает данная функция. Во-первых, она создает обработчик для нового тега – {component}, во-вторых, достает (get_registered_object()) и делает доступными вызванному компоненту зарегистрированные ранее общие объекты.

Немножко теории. Чтобы вызвать данную функцию к жизни, необходимо в любом шаблоне написать тэг {component}. Поскольку функция не выполняет никаких продуктивных действий, и служит исключительно диспетчером компонентов, просто написать {component} недостаточно, поскольку эта команда неинформативна. Конечно, функция не даст сбоя, и запустит принятый по умолчанию компонент 'site_view'. Каким образом уточнить, какой именно компонент вам требуется и как передать ему дополнительные параметры? Все очень просто, мы несколько измени вызов компонента в шаблоне:

{component name='имя требуемого компонента' var1='значение параметра' var2='значение другого параметра'}

И так далее, как вы понимаете, name, var1, var2 – это дополнительные параметры для нашего компонента. Name, например, указывает имя компонента, который мы хотим вызвать, а остальные параметры – это дополнительные данные для вызываемого компонента, их количество может быть любым, ровно как и их имена. После того, как в шаблоне встретится указанный выше тег, произойдет вызов нашей функции, которая, в свою очередь, вызовет нужный вам компонент. Все дополнительные параметры будут доступны как в нашей функции так и в вызываемом компоненте, они будут содержаться в массиве $params, где ключ – это название параметра, а значение – соответственно – его значение.

Мы приняли, что файлы наших компонентов именуются следующим образом:

name.component.php
где name – это наш параметр name.

Функция ищет в указанной папке файл с именем, сформированным указанным образом и исполняет его. В случае отсутствия параметра name запускает компонент, назначенный по умолчанию, в противном случае – выдает ошибку.

Помимо зарегистрированных ранее общих объектов, нашим компонентам полностью доступна вся мощь шаблонизатора Smarty – у вас есть объект $smarty. Обращаю также внимание, что все что вы ассигните (assign) в объект $smarty, будет также доступно не только вашему компоненту, но и общему (вызывающему) шаблону, а это значит, что компоненты вовсе не обязаны всегда выводить что-либо на экран, они вполне могут передавать подготовленные данные в общий шаблон, а он, в свою очередь, может передать их другому компоненту, который на основе их может что-либо вывести на экран.

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

Также отмечу, что вложенность компонентов может быть бесконечной. То есть компонент в своей работе может запустить какой-то свой шаблон, который, в свою очередь, может вызвать другой компонент.

Компонент

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

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

Давайте предположим, что наш компонент выводит новости на главную страницу. Новостей там может быть не более 5 за раз.

В шаблоне напишем так:

{component name='news' action='view' count=5 tpl='news_mainpage.tpl'}

Параметр name указывает на нужный нам компонент, action обозначает действие, которое требуется совершить, count – это фильтр количества новостей, и tpl – название шаблона, который будет использован для формирования вывода новостей на экран.

Теперь давайте посмотрим, как мог бы выглядеть наш компонент:

<?php
// News component
//
// @author Feskov Kuzma

// Подключаем необходимый нам класс
// и создаем объект
require_once(CLASSES_DIR . '/news.class.php');
$news = new News($adodb);

// Обрабатываем возможные действия
switch ($params['action']) {
    
// --- View news ---
    
case 'view':
    
// Получаем 5 последних новостей
    
$data = $news->NewsList($params['count']);
    if (
false === $data) {
        
// Выводим ошибку
        
$errors->ComponentErrPrint('Компонент news, действие ' . $params['action'], $news->ERROR);
    }
    
    
// Проверяем - есть ли такой шаблон
    
if(is_file(TEMPLATES . '/' . $params['tpl'])) {
        
// Выводим новости на экран
        
$smarty->assign('data', $data);
        
$smarty->display($params['tpl']);
    } else {
        
// Выводим ошибку
        
$errors->ComponentErrPrint('Компонент news, действие ' . $params['action'], 'Шаблон не найден');
    }
    break;
    
// --- Default action ---
    
default:
    
// Если никакого действия не задали, выводим ошибку
    
$errors->ComponentErrPrint('news_view', 'Неизвестное действие');
    break;
}
?>

Этот пример показывает упрощенную схему работы компонента. Вы вполне можете ее развить и изменить под свои нужды.

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

Обмен данными между компонентами

Немного остановлюсь на разделении компонентов на типы: prepare – эти компоненты ничего не выводят на экран, они только подготавливают данные для других компонентов или общего (главного) шаблона, и view – эти компоненты обязательно в результате своей работы что-то выводят на экран.

Давайте посмотрим, каким образом компонент prepare может подготовить данные для компонента view.

<?php
// --- Фрагмент компонента prepare ---
$smarty->assign('prepare_data', $prepare_data);
?>

Из данного фрагмента видно, что компонент prepare создал какой-то набор данных и ассигнул (assign) его в главный шаблон, присвоив этим данным имя 'prepare_data'.

Теперь давайте взглянем на главный шаблон:

<!-- Фрагмент главного шаблона -->

<!-- Вызываем компонент prepare -->
{component name='prepare'}

<!-- Вызываем компонент view -->
{component name='view' prepare_data=$prepare_data}

Как видно из этого фрагмента, сначала мы вызвали компонент prepare, а затем компонент view. В качестве параметра передали в view продукт деятельности компонента prepare.

<?php
// --- Фрагмент компонента view ---
print_r($params['prepare_data']);
?>

В свою очередь, компоненту view эти данные стали доступны посредствам массива $params, в котором появился новый индекс – prepare_data.

Итоги

Итак, здесь мы с вами рассмотрели несколько необычную роль шаблонизатора Smarty в разработке приложений: поручили Smarty быть не только удобным шаблонизатором, но и диспетчером нашего приложения, отдали ему на откуп формирование сайта.

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

Компонентная модель разработки позволяет добиться существенного разграничения труда программистов, работающих в команде.

На этом все, надеюсь этот материал даст вам повод для новых изысканий и идей.

Яндекс цитирования