В этом цикле статей я хочу показать, как создать портлетную платформу. Моя главная цель — в лучшем виде презентовать проект, к разработке которого я возвращаюсь время от времени. Каждая статья будет снабжаться ссылкой на GitHub. По окончанию цикла там будет открыт исходный код всего проекта. Материалы первой статьи — уже там.
На текущий момент у меня есть портлетная платформа, написанная на PHP. Сразу прошу web-разработчиков, не знакомых с PHP, но использующих в своей работе Java, C#, Python или любой другой язык, не проходить мимо. Я сам, прежде всего, Java-разработчик, и упор в цикле статей будет поставлен не на код, а на техники и архитектурные приёмы. Кроме того, результат может оказаться полезен разработчику на любом языке. Мне, даже в проектах, разрабатываемых на Java, результат — очень полезен.
Мне кажется неправильным начинать цикл с «большого оглавления», так как оно обязательно окажется слишком пространным, а заключённая в нём логика Автора всё равно может оказаться неочевидной и неинтересной. Поэтому я начну с описания тех волшебных элементов, на которых основано всё остальное. Дальше, если будет интерес, мы уточним и углубимся.
Шаг 1. Описание портлета
Чтобы описать, зачем нужны портлеты, я должен дать сразу две ссылки. Первую, на статью про портлеты в википедии, я уже дал. Вторая ссылка пусть будет на описание паттерна Composite View на странице проекта Apache Tiles. Я предлагаю смотреть на элементы страницы (шапку, меню, блок текста, таблицу с данными, форму поиска) как на портлеты или даже виджеты, размещаемые на странице. При этом главное при построении интерфейса, состоящего из такого набора компонент, это не возможность собрать всё из маленьких кирпичиков, а возможность наследовать и расширять уже имеющиеся интерфейсы.
При этом важно, что далее мы будем использовать именно Composite View, а не Decorator. Сравнение обоих паттернов есть по второй ссылке.
Описание портлета будет выглядеть примерно так:
<?xml version="1.0" encoding="UTF-8"?> <structure xmlns:xi="http://www.w3.org/2001/XInclude"> <definition name="widget-menu" extends="widget" template="public/widgets/menu/menu-content.tpl"> <put-attribute name="title" value="Menu" /> <put-attribute name="controller" value="/widgets/widget.php" /> <!-- Конфигурационные параметры --> </definition> </structure>
Начиная с какой-то статьи станет понятно, зачем нам нужно такое описание, и почему именно в виде XML-документа. В первой же статье я предлагаю всего-лишь разобраться как можно удобно использовать XML-описания и паттерн CV при построении пользовательских интерфейсов.
Представим себе задачу:
Мы разрабатываем свой собственный портал туристической тематики и у нас в разработке около 30 страниц, составленных из разных элементов, среди которых: всевозможные списки с наборами услуг, формы поиска и бронирования, секции с рекламными баннерами, секции с картами и виджетами погоды. На главной странице одновременно используется около 20 элементов, на внутренних — по 10-15. При этом у нас довольно большое число схожих страниц.
В таком случае нам важно уметь «наследовать» страницы, для этого мы будем использовать специальную XML-нотацию описания шаблона и страницы.
<?xml version="1.0" encoding="UTF-8"?> <structure xmlns:xi="http://www.w3.org/2001/XInclude"> <definition name="public-layout" template="content/public/public-layout.tpl"> <put-attribute name="header" value="content/public/public-header.tpl" /> <put-attribute name="content" value="content/public/public-content.tpl" /> <put-attribute name="footer" value="content/public/public-footer.tpl" /> <put-list-attribute name="jsFiles"> <put-attribute value="//code.jquery.com/jquery-2.1.3.min.js" /> <put-attribute value="//maxcdn.bootstrapcdn.com/bootstrap/3.3.4/js/bootstrap.min.js" /> <put-attribute value="/assets/js/app.js" /> </put-list-attribute> <put-list-attribute name="cssFiles"> <put-attribute value="//maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css" /> <put-attribute value="/assets/css/layout.css" /> </put-list-attribute> </definition> <definition name="home" extends="public-layout"> <put-attribute name="content" value="content/public/home/home-content.tpl" /> <put-list-attribute name="jsFiles"> <put-attribute value="/assets/js/home.js" /> </put-list-attribute> </definition> <definition name="about" extends="public-layout"> <put-attribute name="content" value="content/public/about/about-content.tpl" /> <put-list-attribute name="jsFiles"> <put-attribute value="/assets/js/about.js" /> </put-list-attribute> </definition> </structure>
Корневой XML-элемент structure состоит из набора объявлений <definition>…</definition>. Каждое такое объявление может быть найдено по уникальному имени. И каждое объявление сожержит набор собственных свойств и может быть унаследовано от другого. В нашем случае объявлен шаблон public-layout и от него унаследованы 2 страницы: home и about.
Большинство критиков паттерна CV ненавидят подобные XML-документы и отказываются их использовать в повседневной практике. Некоторым из них больше подходит Decorator (Java- и Groovy-программисты могут вспомнить SiteMesh), некоторые используют CV неявно, перекладывая функции управления страницами на используемую CMS-систему.
Мы не будем использовать Decorator, потому что он недостаточно функционален для нашей задачи, а CMS-систему — потому что мы, в какой-то мере, придерживаемся «альтернативных взглядов» на веб-разработку и, на самом деле, хотим показать, как можно не использовать CMS и не потерять в скорости разработки, а также сохранить преемственность кода и снизить порог входа для начинающих разработчиков.
Но и ошибок тех, кто всё-таки слишком увлекается написанием XML-подобных языков, мы повторять не будем. Мы просто используем пару устоявшихся практик при работе с подобными описаниями:
- Разобъём одно описание на много маленьких при помощи XML-схемы xi.
- При разборе XML-документа используем динамическую природу языка PHP и возможности библиотеки SimpleXML.
<?xml version="1.0" encoding="UTF-8"?> <structure xmlns:xi="http://www.w3.org/2001/XInclude"> <xi:include xpointer="xpointer(//definition)" href="structure-private.xml" /> <xi:include xpointer="xpointer(//definition)" href="structure-public.xml" /> </structure>
<?xml version="1.0" encoding="UTF-8"?> <structure xmlns:xi="http://www.w3.org/2001/XInclude"> <xi:include xpointer="xpointer(//definition)" href="structure-public-layout.xml" /> <xi:include xpointer="xpointer(//definition)" href="structure-public-home.xml" /> <xi:include xpointer="xpointer(//definition)" href="structure-public-about.xml" /> </structure>
<?xml version="1.0" encoding="UTF-8"?> <structure xmlns:xi="http://www.w3.org/2001/XInclude"> <definition name="public-layout" template="content/public/public-layout.tpl"> <put-attribute name="header" value="content/public/public-header.tpl" /> <put-attribute name="content" value="content/public/public-content.tpl" /> <put-attribute name="footer" value="content/public/public-footer.tpl" /> <put-list-attribute name="jsFiles"> <put-attribute value="//code.jquery.com/jquery-2.1.3.min.js" /> <put-attribute value="//maxcdn.bootstrapcdn.com/bootstrap/3.3.4/js/bootstrap.min.js" /> <put-attribute value="/assets/js/app.js" /> </put-list-attribute> <put-list-attribute name="cssFiles"> <put-attribute value="//maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css" /> <put-attribute value="/assets/css/layout.css" /> </put-list-attribute> </definition> </structure>
<?xml version="1.0" encoding="UTF-8"?> <structure xmlns:xi="http://www.w3.org/2001/XInclude"> <definition name="home" extends="public-layout"> <put-attribute name="content" value="content/public/home/home-content.tpl" /> <put-list-attribute name="jsFiles"> <put-attribute value="/assets/js/home.js" /> </put-list-attribute> </definition> </structure>
<?xml version="1.0" encoding="UTF-8"?> <structure xmlns:xi="http://www.w3.org/2001/XInclude"> <definition name="about" extends="public-layout"> <put-attribute name="content" value="content/public/about/about-content.tpl" /> <put-list-attribute name="jsFiles"> <put-attribute value="/assets/js/about.js" /> </put-list-attribute> </definition> </structure>
В наших дальнейших исследованиях нам важны будут 2 вещи:
- Подобные «маленькие» XML-документы можно «динамически доставлять»;
- Структуру XML-документа можно фиксировать через использование схемы.
Я дальше не могу придумать ничего умнее, чем предложить на суд около 250 строк кода на PHP, которые обеспечивают обработку описанных XML-документов. Кому интересно, посмотрите, остальные могут принять как данное, что подобный код написать несложно и сосредоточится на механизмах использования.
Ссылка обработчик XML-документов на GitHub
А вот код, использующий функции класса StructureBuilder, ответственного за обработку, личше привести полностью:
<?php $root = __DIR__; require_once "$root/classes/StructureBuilder.class.php"; $structure = StructureBuilder::buildFromFile("$root/structure/structure.xml"); $fragments = empty($_SERVER['PATH_INFO']) ? array() : explode('/', parse_url($_SERVER['PATH_INFO'], PHP_URL_PATH)) ; $directory = count($fragments) > 1 && !empty($fragments[2]) ? urldecode($fragments[2]) : 'home' ; $definition = isset($structure[$directory]) ? $structure[$directory] : $structure['home'] ; require $definition->template; ?>
В этом коде происходит следующее:
Из урла вроде http: // example.com / about мы вычленяем название «раздела», например about. Далее загружаем описания страниц из файла structure.xml. Все внутренние XML-документы загружаются и разбираются автоматически.
Мы не отметили только содержимое шаблона public-layout и всех файлов с расширением tpl, отмеченных в XML-описании. В этих файлах должен лежать код слоя представления. Для максимальной простоты приведём пример подобного кода с простыми <?php ?>-включениями:
<!DOCTYPE html> <html> <head> <?php foreach ($definition->params['cssFiles'] as $key=>$value) { ?> <link href="<?php echo $value; ?>" rel="stylesheet" type="text/css" /> <?php } ?> <?php foreach ($definition->params['jsFiles'] as $key=>$value) { ?> <script type="text/javascript" src="<?php echo $value; ?>"></script> <?php } ?> </head> <body> <div class="l_header"><?php include "$root/{$definition->params['header']}"; ?></div> <div class="l_content"><?php include "$root/{$definition->params['content']}"; ?></div> <div class="l_foorer"><?php include "$root/{$definition->params['footer']}"; ?></div> </body> </html>
Видно, что мы:
- Каждую страницу составляем из «фрагментов», «лепестков» или, в дальнейшем, «портлетов»;
- На каждой странице подключаем необходимые ресурсы, скрипты и стили, причём списки ресурсов наследуются страницами друг от друга.
Код конечных страниц оказывается очень простым:
<section class="home-content"> Home Content </section>
Вот и всё, для первой статьи в цикле — вполне достаточно. Теперь все, кому нужно, могут в своих проектах воспользоваться паттерном CV и шаблоном проекта, размещённым на GitHub.
В следующей статье, скорее всего, мы уточним понятие портлета и я немного расскажу о паттерне Request Dispatcher. И сделаю я это (как это связано, спросите вы) с использованием незаслуженно забытого шаблонизатора Smarty. А разработчикам на Java я обещаю в следующей статье также рассказать про устройство Servlet API.
Полезные ссылки
Реализация Composite View на Java, послужившая прототипом для нашей реализации на PHP. Хорошо дружит с JSP, Struts 2, Jersey MVC.
ссылка на оригинал статьи http://habrahabr.ru/post/255247/
Добавить комментарий