Pull to refresh

Пошаговые инструкции по созданию портлетной платформы: Описание портлета

Reading time 9 min
Views 5.1K
В этом цикле статей я хочу показать, как создать портлетную платформу. Моя главная цель — в лучшем виде презентовать проект, к разработке которого я возвращаюсь время от времени. Каждая статья будет снабжаться ссылкой на 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-документа
<?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-подобных языков, мы повторять не будем. Мы просто используем пару устоявшихся практик при работе с подобными описаниями:

  1. Разобъём одно описание на много маленьких при помощи XML-схемы xi.
  2. При разборе XML-документа используем динамическую природу языка PHP и возможности библиотеки SimpleXML.

structure.xml
<?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>


structure-public.xml
<?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>


structure-public-layout.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>

</structure>


structure-public-home.xml
<?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>


structure-public-about.xml
<?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 вещи:

  1. Подобные «маленькие» XML-документы можно «динамически доставлять»;
  2. Структуру 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>

Видно, что мы:

  1. Каждую страницу составляем из «фрагментов», «лепестков» или, в дальнейшем, «портлетов»;
  2. На каждой странице подключаем необходимые ресурсы, скрипты и стили, причём списки ресурсов наследуются страницами друг от друга.

Код конечных страниц оказывается очень простым:

<section class="home-content">
	Home Content
</section>

Вот и всё, для первой статьи в цикле — вполне достаточно. Теперь все, кому нужно, могут в своих проектах воспользоваться паттерном CV и шаблоном проекта, размещённым на GitHub.

В следующей статье, скорее всего, мы уточним понятие портлета и я немного расскажу о паттерне Request Dispatcher. И сделаю я это (как это связано, спросите вы) с использованием незаслуженно забытого шаблонизатора Smarty. А разработчикам на Java я обещаю в следующей статье также рассказать про устройство Servlet API.

Полезные ссылки


Реализация Composite View на Java, послужившая прототипом для нашей реализации на PHP. Хорошо дружит с JSP, Struts 2, Jersey MVC.
Tags:
Hubs:
+7
Comments 0
Comments Leave a comment

Articles