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

в 20:20, , рубрики: easyweb, geoip, solr, XML, xquery, xslt, Веб-разработка, метки: , , , , ,

В моем предыдущем посте, представившем многоуважаемой публике веб-движок 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-а.

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

Автор: nyan

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js