PHP-расширение dom_varimport: быстрое преобразования вложенных массивов в DOMDocument

    Некоторые проекты используют XSLT в качестве основного «движка» шаблонов. Помимо известных недостатков XSLT (например, его многословности, относительной медлительности и т.д.) у него есть и преимущества: «стандартность» языка, его идеология отсутствия «побочных эффектов» и pattern matching, возможность при необходимости вызывать методы helper-классов из шаблонов (через exslt-расширение). Какое-то время назад я выкладывал библиотеку ShortXSLT, позволяющую вместо громоздких <xsl:value-of select="/root/abc"/> и <xsl:choose>...</xsl:choose> писать просто {/root/abc} и {if...}...{elseif}...{/if} без потери производительности, так что проблема многословности отчасти решается.

    Но сейчас речь не о преимуществах и недостатках XSLT (я уверен, и противники, и сторонники этой технологии найдутся в изобилии). Я бы хотел описать один прием, который удобно применять в существующих проектах с XSLT-шаблонами, и привести ссылку на библиотеку, реализующую данный прием с хорошей производительностью.

    Передаем данные в XSLT, минуя генерацию текстового представления XML

    Представьте, что у нас есть контроллер, генерирующий некоторый вложенный PHP-список объектов для отображения на странице. Он должен этот массив преобразовать в XML, который потом пойдет на вход XSLT-шаблону. Хорошо бы, чтобы данное преобразование из структур PHP в XML выполнялось не вручную в каждом контроллере, а был некоторый промежуточный слой абстракции, который умеет применять XSLT-шаблон прямо к PHP-данным, минуя текстовое XML-представление. Так мы уменьшим вероятность ошибок, да и письмо сократится. Мы сможем работать с XSLT-шаблонами напрямую, минуя XML-представление данных.

    Некоторое время назад я написал на Си PHP-расширение dom_varimport (также выложено на GitHub). Оно содержит одноименную функцию, на вход которой подается объект DOMDocument и PHP-массив любой вложенности. Функция заполняет переданный ей DOMDocument XML-представлением входного массива, и делает она это очень быстро — примерно в 20 раз быстрее, чем делал бы код, написанный на чистом PHP. Большой документ размером около 1 МБ с тысячами вложенных свойств и объектов формируется примерно за 1-2 миллисекунды.

    Например, вызов:

    $doc = new DOMDocument();
    dom_varimport(
        $doc,
        array(
            "some_key" => 111,
            123,
            0.5,
            "arr" => array("1a" => "1a"),
            "obj" => (object)array("prop" => "val"),
            true,
            false,
            "b" => null,
            "empty" => array(),
        ),
        "root" // optional, defaults to "root"
    );
    $doc->formatOutput = true;
    echo $doc->saveXML(); // это только для отладки: на практике вам не нужно будет вызывать saveXML()
    

    напечатает вот такой XML-документ:

    <?xml version="1.0"?>
    <root>
        <some_key key="some_key">111</some_key>   <!-- plain key=value -->
        <item key="0">123</item>         <!-- numeric keys are "item" tags -->
        <item key="1">0.5</item>         <!-- double -->
        <arr key="arr">                  <!-- nested array -->
            <item key="1a">1a</item>         <!-- invalid tag names are converted to "item" -->
        </arr>
        <obj key="obj">                  <!-- nested object -->
            <prop key="prop">val</prop>
        </obj>
        <item key="2">1</item>           <!-- true converts to 1 -->
        <item key="3"/>                  <!-- false converts to an empty string -->
        <b key="b"/>                     <!-- null also converts to an empty string -->
        <empty key="empty"/>             <!-- empty array is an empty element -->
    </root>
    

    Все достаточно прозрачно: ключи массива и свойства объектов становятся XML-элементами, по возможности с теми же именами (но если имя недопустимо для XML-элемента, то вместо него используется «item»). Такой XML-документ очень легко читать при отладке, он весьма компактен. Итак, мы получаем на выходе объект DOMDocument, который уже можем передать XSLTProcessor-у. Текстовое представление XML нигде не фигурирует, нигде не парсится.

    Как установить расширение

    Расширение написано на Си, поэтому его нужно откомпилировать на машине, на которой установлены GCC и пакеты типа php5-src (или php5-devel). Это совсем не страшно:

    git clone https://github.com/DmitryKoterov/dom_varimport.git
    cd dom_varimport
    phpize
    ./configure
    make
    make test
    make install  # or copy modules/dom_varimport.so manually
    phpize --clean
    

    Этап «make install» можно и не делать: достаточно взять бинарный файл modules/dom_varimport.so и скопировать его в директорию с расширениями PHP (например, /usr/lib/php5), в том числе и на других машинах. Наконец, нужно подключить расширение в /etc/php5/conf.d/dom_varimport.ini и перезапустить php5-fpm или apache:

    extension = /usr/lib/php5/dom_varimport.so
    
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 11

      0
      4 года назад вышли из ситуации похожим образом. Массивы очень долго преобразовывались в xml при помощи стандартных средств PHP. А шаблонов было слишком много, чтобы в разумные сроки избавиться от XSLT. Тогда собрал «на коленке» либу, на основе стандартной функции json_encode, переписав её немного. С тех пор так и работает.
        0
        Давно уже делал тесты, оказалось, что гораздо быстрее работать/формировать XML через SimpleXML, и преобразовывать его в DOM уже непосредственно перед XSLT трансформацией.

        А с SimpleXML довольно удобно работать, не хуже, чем с массивами, плюс, есть XPath для поиска значений.

        <?php
        $data = new SimpleXMLElement('<data/>');
        //...
        $doc = new DomDocument();
        $doc->loadXml($data->asXml());
        $xslt = new DomDocument();
        $xslt->load('template.xslt');
        $proc = new XsltProcessor();
        $proc->registerPHPFunctions();
        $proc->importStylesheet($xslt);
        $doc = $proc->transformToUri($doc, 'php://output');
        
        
          0
          Все верно, только код на чистом PHP, использующий SimpleXML или DOMDocument, примерно в 20 раз медленнее, чем dom_varimport (по моим оценкам). Во многих случаях это не держит, но мне как-то раз жалко стало 40 мс, и вот, родилось данное расширение…
            0
            Про сравнение DOMDocument с dom_varimport поверю, но не с SimpleXML, он очень быстр.
          0
          Избавиться от многословного синтаксиса можно с помощью небольшого дополнительного шаблона: «Добавляем сахар в XSLT».
            0
            А почему не распространять через PECL, к чему эти пляски с make?
              +1
              В PECL добавить — процесс небыстрый. Я его запустил одновременно с написанием этого поста.
                0
                Большое спасибо и прошу простить на негативный комментарий.
              0
              Странная область применения, собирать данные для выдачи в пхпшные массивы, потом из них слепить xml объект, который отдать xslt процессору. Если используется такая замечательная технология как xslt, то зачем данные собирать в массивы — можно сразу строить xml-объект нужной структуры, разница будет не заметна :)
              Расширение должно быть полезное для импорта, но упоминание xslt только сбивает с толку.
              А ещё лучше бы сразу из пхпшной сериализации строило xml объектины, эдакий unserialize_to_xml :)
                0
                Можно и руками формировать, но это будет хардкод и копипаст, размазанный ровным слоем по всем контроллерам проекта. Если у вас используется ORM (да или даже PDO простой, или что угодно), то результат sql-выборок естественным образом будет помещен в PHP-массивы или PHP-объекты. Зачем в каждом контроллере нужно это все итерировать и формировать XML, когда можно данную операцию сделать централизованно?
                0
                когда-то я был фаном XSLT, даже где-то были мои статьи :)
                но со временем отказался от этой технологии в пользу более простых и быстрых шаблонизаторов

                Only users with full accounts can post comments. Log in, please.