1. Структура проекта
Обычно страница сайта состоит из нескольких общих блоков (меню, футер, …) и контентной части, которую для общности будем называть основным блоком. Все эти блоки размещаются внутри некоторого индексного шаблона, который знает в каком месте какой блок отобразить: меню должно быть вверху, основной блок в центре, а футер внизу.
Получаем следующую структуру
/themes - здесь раполагаются все шаблоны /themes/index/main.xsl - индексный шаблон /themes/models/user.xsl - именованные шаблоны, которые относятся к модели пользователя /themes/inc/functions.xsl - именованные шаблоны-функции /themes/blocks/footer.xsl - шаблон футера /themes/blocks/menu.xsl - шаблон меню /themes/cabinet/main.xsl - шаблон основного блока главной страницы кабинета пользователя
Контроллер главной страницы кабинета пользователя работает следующим образом:
- получает данные для основного блока, обрабатывает их с помощью /themes/cabinet/main.xsl и результат (готовый html) помещает в итоговый xml
- аналогично обрабатывает данные для других блоков (меню, футер) и результат помещает в xml
- итоговый xml, в котором находятся данные всех блоков, обрабатывается с помощью индексного шаблона /themes/index/main.xsl и результат отдаёт пользователю.
Индексный шаблон /themes/index/main.xsl может выглядет следующим образом:
<xsl:template match="page"> <head> <title><xsl:value-of select="title" /></title> </head> <body> <div class="page-container"> <xsl:value-of select="blocks/menu_top/html" disable-output-escaping="yes"/> <div class="main"> <xsl:value-of select="blocks/content/html" disable-output-escaping="yes"/> </div> <xsl:value-of select="blocks/footer/html" disable-output-escaping="yes"/> </div> </body> </xsl:template>
2. Именованные шаблоны
Шаблон xslt принимает данные в виде xml-документа. Это удобно тем, что мы можем оперировать целыми узлами. Например для вывода имени пользователя у нас может быть такой шаблон
<xsl:template name="inc_show_user"> <xsl:param name="user"/> <img src="/img/{$user/userpic}.png"/> <xsl:value-of select="concat($user/first_name, ' ', $user/last_name)"/> </xsl:template>
который располагается в файле /themes/models/user.xsl.
Мы можем использовать этот шаблон как для отображения текущего пользователя
<xsl:call-template name="inc_show_user"> <xsl:with-param name="user" select="/*/cur_user"/> </xsl:call-template>
так и для отображения списка пользователей
<xsl:for-each select="users/item"> <xsl:call-template name="inc_show_user"> <xsl:with-param name="user" select="."/> </xsl:call-template> </xsl:for-each>
Такое единство отображения сущностей позволяет быстро изменять их отображение. Конечно не у всех пользователей есть картинка, а значит и выводить её нужно не для всех
<xsl:template name="inc_show_user"> <xsl:param name="user"/> <xsl:choose> <xsl:when test="$user/userpic>0"> <img src="/img/{$user/userpic}.png"/> </xsl:when> <xsl:otherwise> <img src="/img/default.png"/> </xsl:otherwise> </xsl:choose> <xsl:value-of select="concat($user/first_name, ' ', $user/last_name)"/> </xsl:template>
3. Импорт шаблонов
Для того чтобы в шаблоне блока иметь доступ к отображению сущности «пользователь» мы должны подключить файл /themes/models/user.xsl.
Для шаблона /themes/cabinet/main.xsl подключение будет выглядеть так
<xsl:import href="../models/user.xsl"/>
(xsl:import должен описываться сразу после xsl:stylesheet)
4. Ни строчки php-кода в представлении
Патерн MVC предполагает разделение модели, логики и представления. Логика приложения запрашивает необходимые данные у модели и передаёт их в представление. Представление должно получить необходимое количество данных, чтобы их отобразить пользователю. Т.е. в представлении мы должны только вывести их и не должны как-либо ещё преобразовывать данные. Мы не должны получать имя пользователя по его id, не должны получать текущее время, и т.д. все эти данные уже должны быть доступны для представления. Если каких-либо данных не хватает, значит контроллер должен их предоставить.
Xslt позволяет производить простейшие операции с данными: сравнение, подсчёт количества, сортировка, форматирование чисел, округление, арифметические операции, конкатенация,… Казалось бы, что это противоречит предыдущему абзацу. Но позвольте заметить, что в результате всех этих операций мы не получаем новых данных, а лишь преобразуем имеющиеся данные.
Не всегда есть все необходимые средства для получения необходимого результата. Например, вывод окончания для числа. Думаю у многих есть подобная функция
function str_plural_form($n, $form1='штука', $form2='штуки', $form5='штук'){ $lastN=$num%10; $lastT=$num%100; if($lastT>=10 && $lastT<=20){ return $form5; } switch ($lastN){ case 1: return $form1; case 2: case 3: case 4: return $form2; default: return $form5; } }
И даже больше, xslt позволяет вызвать эту функцию прямо из шаблона
<xsl:value-of select="php:function('str_plural_form', 1*$cnt_users, 'пользователь', 'пользователя', 'пользователей')"/>
Но это не только противоречит заголовку раздела, но и является неким атавизмом. Лучше избегать вызовов php-функций внутри xslt-шаблонов.
Что же делать? Есть 2 выхода:
- пусть контролер вызывает str_plural_form и отдаёт нужные данные
- сделать именованный шаблон-функцию, которую мы поместим в /themes/inc/functions.xsl
<xsl:template name="f_plural_form"> <xsl:param name="num"></xsl:param> <xsl:param name="format">### ###</xsl:param> <xsl:param name="is_show_num">1</xsl:param> <xsl:param name="space"/> <xsl:param name="str1">штука</xsl:param> <xsl:param name="str2">штуки</xsl:param> <xsl:param name="str5">штук</xsl:param> <xsl:if test="$is_show_num=1"> <xsl:value-of select="format-number($num, $format)"/> <xsl:choose> <xsl:when test="$space!=''"> <xsl:value-of select="$space" disable-output-escaping="yes"/> </xsl:when> <xsl:otherwise> <xsl:text> </xsl:text> </xsl:otherwise> </xsl:choose> </xsl:if> <xsl:variable name="lastN" select="$num mod 10"/> <xsl:variable name="lastT" select="$num mod 100"/> <xsl:choose> <xsl:when test="$lastT>=10 and 20>=$lastT"> <xsl:value-of select="$str5" disable-output-escaping="yes"/> </xsl:when> <xsl:when test="$lastN=1"> <xsl:value-of select="$str1" disable-output-escaping="yes"/> </xsl:when> <xsl:when test="$lastN=2 or $lastN=3 or $lastN=4"> <xsl:value-of select="$str2" disable-output-escaping="yes"/> </xsl:when> <xsl:otherwise> <xsl:value-of select="$str5" disable-output-escaping="yes"/> </xsl:otherwise> </xsl:choose> </xsl:template>
Вызов функции будет выглядеть так
<xsl:call-template name="f_plural_form"> <xsl:with-param name="is_show_num">1</xsl:with-param> <xsl:with-param name="num" select="$cnt_users"/> <xsl:with-param name="str1">пользователь</xsl:with-param> <xsl:with-param name="str2">пользователя</xsl:with-param> <xsl:with-param name="str5">пользователей</xsl:with-param> </xsl:call-template>
5. Справочники
Вернёмся к выводу информации о пользователе. К примеру на странице форума нам нужно вывести
- список постов с именами пользователей,
- список самых активных пользователей,
- список пользователей которые в данный момент просматривают эту страницу.
Можно решить задачу влоб. При получении каждого из списков делать LEFT JOIN users и получать необходимые данные для вывода информации о пользователе. Но есть и отрицательные моменты такого решения. Первое — возможная избыточность данных (пользователи из списков могут повторяться), второе — дополнительная нагрузка на sql-сервер.
Другой вариант решения задачи. Получить все списки. Затем из этих списков получить набор user_id. И по этому набору сделать один запрос к таблице users. Результат сложить в xml по известному адресу, например /ref_users.
В итоге у нас должен получиться xml-документ с узлами posts, active_users, online_users, ref_users.
Для вывода информации о пользователе сделаем такой именованный шаблон
<xsl:template name="inc_show_user_by_id"> <xsl:param name="user_id"/> <!-- поиск пользователя в справочнике по его id --> <xsl:variable name="cur_user" select="/*/ref_users/item[user_id=$user_id]"/> <xsl:call-template name="inc_show_user"> <xsl:with-param name="user" select="$cur_user"/> </xsl:call-template> </xsl:template>
и сохраним его в /themes/models/user.xsl. Это шаблон для вывода пользователя по его id.
Вывести список постов с информацией о пользователе можно так
<xsl:for-each select="posts/item"> <xsl:call-template name="inc_show_user_by_id"> <!-- передаём в шаблон user_id автора поста --> <xsl:with-param name="user_id" select="user_id"/> </xsl:call-template> <!-- далее вывод самого поста --> <!-- ... --> </xsl:for-each>
Заключение
Статья получилась объёмной, поэтому не рассмотренными остались вопросы организации шаблонов для ajax, «абстрактные шаблоны», поддержка нескольких языков. А также вопросы скорости и кеширования.
ссылка на оригинал статьи http://habrahabr.ru/post/151241/
Добавить комментарий