В моем предыдущем посте, представившем многоуважаемой публике веб-движок Easyweb, было сказано:
Планируется, что первая версия, которую можно будет назвать стабильной и полностью юзабельной, появится до конца года.
Поскольку до конца года осталось менее суток, то позвольте рассказать о том, что еще удалось сделать в этом году.
XML facilities
Среди прочих задач, поставленных перед Easyweb-ом, была необходимость сделать работу с XML на стороне PHP максимально чистой, изящной, компактной и простой для понимания. В данный момент дописаны все основные методы XML-фасилитей. Описание публичных методов и небольшие примеры использования:
- XML document: github.com/nyan-cat/easyweb/wiki/class-xml
- XML node set: github.com/nyan-cat/easyweb/wiki/class-nodeset
- XML node: github.com/nyan-cat/easyweb/wiki/class-node
Как и всегда, любую дополнительную функциональность можно попросить через 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>
Возможный результат:
Кеширование блоков
Теперь результат 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-базу качать нужно отдельно:
- Установка бесплатной базы: http://www.maxmind.com/en/installation?city=1
- Более подробная платная версия: http://www.maxmind.com/en/city
- Ну, или на торрентах ;-)
Полнотекстовый и фасетный поиск
Появилась первая пробная версия полнотекстового и фасетного поиска. Поиск выполняется через 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-а.
С Новым годом!
Автор: nyan