Easyweb: Новогоднее обновление

от автора

В моем предыдущем посте, представившем многоуважаемой публике веб-движок Easyweb, было сказано:

Планируется, что первая версия, которую можно будет назвать стабильной и полностью юзабельной, появится до конца года.

Поскольку до конца года осталось менее суток, то позвольте рассказать о том, что еще удалось сделать в этом году.

XML facilities

Среди прочих задач, поставленных перед Easyweb-ом, была необходимость сделать работу с XML на стороне PHP максимально чистой, изящной, компактной и простой для понимания. В данный момент дописаны все основные методы XML-фасилитей. Описание публичных методов и небольшие примеры использования:

Как и всегда, любую дополнительную функциональность можно попросить через feature request на GitHub-е.

Приведу пример решения одной и той же задачи на стандартном PHP DOM API, и на Easyweb XML facilities. Суть такова. Нужно загрузить файл с описанием книг, и сделать с каждой книгой следующее: обнулить цену, заменить код валюты на его написание в нижнем регистре, а также установить заданные идентификаторы для автора и категории книги (включая проверку ошибок).

Было:

<?php function replace($author_id, $category_id) {     $xml = new DOMDocument();     if(!$xml->load('/var/www/html/mywebsite/xml/library.xml'))     {         throw new Exception('Error loading XML file');     }     $xpath = new DOMXPath($xml);     foreach($xpath->query('/books/book') as $book)     {         $price = $xpath->query('price', $book);         if($price->length != 1)         {             throw new Exception('Node "price" should be unique for the book');         }         $price->item[0]->nodeValue = 0;          $currency = $price->getAttribute('currency');         if($currency)         {             $book->setAttribute('currency', strtolower($currency));         }         else         {             throw new Exception('Attribute "currency" not found');         }          $book->setAttribute('author_id', $author_id);         $book->setAttribute('category_id', $category_id);     } } ?> 

Стало:

<?php function replace($author_id, $category_id) {     $xml = xml::load('/xml/library.xml');     foreach($xml->query('/books/book') as $book)     {         $book['price'] = 0;         $book['price/@currency'] = strtolower($book['price/@currency']);         $book['@author_id'] = $author_id;         $book['@category_id'] = $category_id;     } } ?> 

Easyweb XML фасилити умеют конструироваться от нативных PHP DOM resource handle, а также выдавать их пользователю через мембер-функцию ::get(), в связи с чем можно легко интегрироваться со сторонними библиотеками, работающими через нативные PHP DOM объекты.

XPath-расширение www:paginate

www:paginate($page, $count, $size)

Функция предназначена для упрощения отрисовки статического пагинатора.
$page — текущая страница.
$count — общее количество страниц.
$size — размер пагинатора.
Текущая страница отмечается атрибутом current. На первой странице не будет ноды <previous />, на последней — ноды <next />.

Вызов функции www:paginate(15, 85, 10) вернет вот такой XML:

<pages>     <previous>14</previous>     <page>10</page>     <page>11</page>     <page>12</page>     <page>13</page>     <page>14</page>     <page current="current">15</page>     <page>16</page>     <page>17</page>     <page>18</page>     <page>19</page>     <next>16</next> </pages> 

Пример верстки:

<xsl:template match="/">     <xsl:apply-templates select="www:paginate(15, 85, 10)/pages/*" /> </xsl:template> <xsl:template match="previous">     <a href="/page/{.}/" class="page">← Previous</a> </xsl:template> <xsl:template match="next">     <a href="/page/{.}/" class="page">Next →</a> </xsl:template> <xsl:template match="page[@current]">     <span class="page current"><xsl:value-of select="." /></span> </xsl:template> <xsl:template match="page">     <a href="/page/{.}/" class="page"><xsl:value-of select="." /></a> </xsl:template> 

Возможный результат:
image

Кеширование блоков

Теперь результат XSL-расширения www:xslt можно кешировать в файлы. Для этого нужно добавить атрибут cache="true". Также имеется два необязательных атрибута cache-args и cache-lifetime, первый из которых позволяет передать в закешированный блок список простых параметров, а второй — ограничить время жизни закешированных данных. Пример использования:

<www:xslt     xsl="/books.xsl"     xml="book:list(author_id -> {$author_id})"     args="page -> {$page}, count -> 10"     cache="true"     cache-args="domain -> '{$domain}'"     cache-lifetime="600" /> 

XQuery

Первый вариант поддержки XQuery в Easyweb. На данный момент его возможности сильно ограничены: нельзя передать параметры, нельзя использовать XSL- и XPath-расширения Easyweb-а. Главная проблема сейчас заключается в том, что хорошую XQuery-библиотеку для PHP не удалось найти в принципе. Если вы можете помочь мудрым советом, то буду рад услышать его здесь: habrahabr.ru/qa/31087/

На данный момент XQuery сделан через XQuery Lite (http://phpxmlclasses.sourceforge.net/xquery_lite.html), который был сделан и заброшен его автором еще в 2002-м году. XQuery Lite выложен в репозиторий Easyweb в связи с тем, что его пришлось допилить напильником, чтобы он заработал в PHP5.

Сейчас поддержка XQuery заключается во введении XSL-расширения www:xquery:

<div>     <h1>External Resources</h1>     <www:xquery src="/tpl/links.xq" /> </div> 

Пользовательские XSL-расширения

Теперь пользователь может регистрировать в движке свои собственные XSL-расширения. Для своего собственного расширения нужно указать пространство имен, а также его URI. То же самое нужно сделать в XSL-шаблонах страниц. Пример регистрации XSL-расширения, реализующего некоторую обработку текста (например — какая-то своя разметка):

$www = www::create('en', 'us');  $www->register_xsl('http://supermarkup.com/about', 'sm', 'block', function($node) {     $xml = new xml();     foreach($node->children() as $child)     {         $xml->append($xml->import($child));     }     foreach($xml->query('//text()') as $text)     {         $parent = $text->parent();         $parent->append(supermarkup($text->value()));         $parent->remove($text);     }     return $xml; }); 

Использование в шаблоне:

<?xml version="1.0" encoding="utf-8" ?>  <xsl:stylesheet version="1.0"     xmlns:xsl="http://www.w3.org/1999/XSL/Transform"     xmlns:php="http://php.net/xsl"     xmlns:www="https://github.com/nyan-cat/easyweb"     xmlns:sm="http://supermarkup.com/about"     exclude-result-prefixes="php www sm">     <xsl:template match="/">         <sm:block>             <xsl:copy-of select="message" />         </sm:block>     </xsl:template> </xsl:stylesheet> 

Функция-обработчик расширения работает через Easyweb XML фасилити. Она принимает на вход XML-ноду, являющуюся расширением, и возвращает либо XML-ноду, либо XML-документ, которыми будет заменена нода расширения.

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

GeoIP

В движке появилась поддержка GeoIP. Для его работы потребуется установить PHP PECL GeoIP. Поддержка GeoIP сделана через интерфейс абстрактных процедур Easyweb-а. Пример описания GeoIP-процедуры в конфиге сайта:

<procedure name="geoip:record" datasource="geoip" method="record" root="record">     <param name="host" type="string" /> </procedure> 

Теперь эту поцедуру можно использовать в любом месте, где используются абстрактиные процедуры Easyweb-а: при рендере страницы, при рендере блока, при вычислении групп системы прав доступа, при вызове XPath-расширения www:query, или же из PHP через инстанс класса Easyweb. Пример вызова:

$record = $www->query('geoip:record', array (     'host' => $www->variable('user:ip') )); 

Возможный результат:

<?xml version="1.0"?> <record>     <country>         <alpha2>US</alpha2>         <alpha3>USA</alpha3>         <name>United States</name>     </country>     <region>NC</region>     <city>Charlotte</city>     <latitude>35.206001281738</latitude>     <longitude>-80.829002380371</longitude> </record> 

Не забудьте, что GeoIP-базу качать нужно отдельно:

Полнотекстовый и фасетный поиск

Появилась первая пробная версия полнотекстового и фасетного поиска. Поиск выполняется через Apache Solr (http://lucene.apache.org/solr/). Для использования поиска потребуется установить Java, Servlet container (например — Tomcat или Jetty), сам Solr, а также PHP PECL SolrClient. Простая и доступная статья по установке Solr-а на CentOS: http://blog.nexcess.net/2011/12/30/installing-apache-solr-on-centos/.

Как и в случае GeoIP, Solr-поиск реализован через абстрактные процедуры Easyweb, которые можно использовать в разных подсистемах движка. Пример конфига:

<datasource     name="metadata"     type="solr"     server="localhost"     port="8080"     url="/solr/"     username="admin"     password="samplepassword" /> <!-- ... --> <procedure name="guestbook:add" datasource="guestbook" core="guestbook" method="add">     <param name="author_id" type="natural" />     <param name="message" type="author" />     <param name="host" type="ipv4" /> </procedure> <!-- ... --> <procedure     name="guestbook:list"     datasource="guestbook"     core="guestbook"     method="query"     root="messages"     item="message">     *:* </procedure> 

Пока что конфигурить Solr нужно самостоятельно. В будущем планируется сделать генератор схемы Solr-а налету.

Заключение

Итак, в движке реализованы все основные функции уровня ядра, котрые были ранее запланированы. В более долгосрочных планах остаются LL(1)-парсеры, ORM-система и фреймворк для полнодуплексного общения клиента и сервера.

Планы на начало следующего года — заняться переездом одного Большого® Коммерческого© Сайта™, использующего все функции Easyweb-а, на новую версию движка, параллельно с чем будут устраняться обнаруженные ошибки.

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

С Новым годом!

ссылка на оригинал статьи http://habrahabr.ru/post/164397/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *