Pull to refresh

Конструктор XML-строки из PHP-массива

Reading time 5 min
Views 12K

Как часто приходится работать с XML PHP-разработчикам? Не так часто, на самом деле. Обычно потребность возникает при интеграции со сторонним сервисом, такие как BetaPRO, OnTime или CDEK. И вот тут обычно возникает такая ситуация, когда ваш код становится похожим на


$date = '2016-09-25T12:45:10';
$account = 'f62dcb094cc91617def72d9c260b4483';
$secure = '81ad561784277fa864bf644d755fb164';
$count = 1;
$copy = 4;
$dispatchNumber = '1033229706';
$orderDate = '2016-09-25T12:45:10';

$request = <<<XML
<?xml version="1.0" encoding="UTF-8"?> 
<OrdersPrint Date="{$date}" Account="{$account}" Secure="{$secure}" OrderCount="{$count}" CopyCount="{$copy}">
    <Order DispathNumber="{$orderNumber}" Date="{$orderDate}"/>
</OrdersPrint>
XML;

и это еще не все! Нужно позаботиться о том, чтобы значения атрибутов и содержимое, заключенное в теги, не содержало спецсимволов, присущие XML. Если для конкретно этого запроса можно быть уверенным, что ничего из спецсимволов сюда не попадет, то контролировать каждый запрос вовсе бы не хотелось. Поэтому через "фильтр" пропускается все. Отсюда следует, что нужно еще "загнаться" с htmlspecialchars или с CDATA, или с XMLWriter, и знать, как это применить и не раз еще "свернуть себе кровь". Как вы видите, времени стоит "убить" достаточно, а результат-то хочется уже сейчас. Эх… А как хотелось бы, чтобы XML можно было бы создавать так же быстро, как JSON: отдал массив, а тебе XML-строку, и никаких заморочек. Опечалившись сложившейся ситуацией я в далеком 2015ом году я решил сделать такой конструктор.


Вашему вниманию представляю xml-constructor для PHP начиная с версии 5.4 и до 7.2 на момент публикации данной статьи.


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


Для начала использования установим данный пакет через Composer:


$ composer require bupy7/xml-constructor

Его так же можно просто скопировать вручную куда вы хотите, т.к. пакет не имеет никаких доп. зависимостей, кроме как наличия libxml в самом PHP.


Теперь создадим XML-строку используя PHP-массив:


$date = '2016-09-25T12:45:10';
$account = 'f62dcb094cc91617def72d9c260b4483';
$secure = '81ad561784277fa864bf644d755fb164';
$count = 1;
$copy = 4;
$dispatchNumber = '1033229706';
$orderDate = '2016-09-25T12:45:10';

$in = [
    [
        'tag' => 'OrdersPrint',
        'attributes' => [
            'Date' => $date,
            'Account' => $account,
            'Secure' => $secure,
            'OrderCount' => $count,
            'CopyCount' => $copy,
        ],
        'elements' => [
            [
                'tag' => 'Order',
                'attributes' => [
                    'DispathNumber' => $dispatchNumber,
                    'Date' => $orderDate,
                ],
            ],
        ],
    ],
];
$request = (new \bupy7\xml\constructor\XmlConstructor())->fromArray($in)->toOutput();

Результат:


<?xml version="1.0" encoding="UTF-8"?>
<OrdersPrint Date="2016-09-25T12:45:10" Account="f62dcb094cc91617def72d9c260b4483" Secure="81ad561784277fa864bf644d755fb164" OrderCount="1" CopyCount="4">
    <Order DispathNumber="1033229706" Date="2016-09-25T12:45:10"/>
</OrdersPrint>

Вот и вся работа! Об остальном позаботится xml-constructor.


И давайте попробуем передать что-то "запрещенное" в значения и посмотрим, как будет вести себя xml-constructor:


$date = '2016-09-25T12:45:10';
$secure = '81ad561784277fa864bf644d755fb164';
$count = 1;
$copy = 4;
$dispatchNumber = '1033229706';
$orderDate = '2016-09-25T12:45:10';

// ACHTUNG !!!
$account = '<example danger="account"><WTF?!/></example>';
$orderContent = '<special>"chars"';

$in = [
    [
        'tag' => 'OrdersPrint',
        'attributes' => [
            'Date' => $date,
            'Account' => $account,
            'Secure' => $secure,
            'OrderCount' => $count,
            'CopyCount' => $copy,
        ],
        'elements' => [
            [
                'tag' => 'Order',
                'attributes' => [
                    'DispathNumber' => $dispatchNumber,
                    'Date' => $orderDate,
                ],
                'content' => $orderContent,
            ],
        ],
    ],   
];

$request = (new \bupy7\xml\constructor\XmlConstructor())->fromArray($in)->toOutput();

Результат:


<?xml version="1.0" encoding="UTF-8"?>
<OrdersPrint Date="2016-09-25T12:45:10" Account="&lt;example danger=&quot;account&quot;&gt;&lt;WTF?!/&gt;&lt;/example&gt;" Secure="81ad561784277fa864bf644d755fb164" OrderCount="1" CopyCount="4">
    <Order DispathNumber="1033229706" Date="2016-09-25T12:45:10">&lt;special&gt;&quot;chars&quot;</Order>
</OrdersPrint>

Требования


Создание XML-строки сводится к тому, что нужно передать PHP-массив с нужными ключами и в правильной структуре. Ключей всего четыре:


  • tag — строка с названием тега;
  • content — содержимое заключенное между тегом;
  • attributes — массив ключ-значение, где ключ — это, название атрибута (строка), а значение — его значение (строка);
  • elements — новая вложенность тегов внутри которого этот ключ был указан. Также будет содержать в себе все перечисленные выше элементы. Вложенность неограниченная.

Каждый элемент массива должен содержать массив с одним ключом tag, как минимум. Ключи attributes, content и elements необязательные.


Первый уровень вложенности есть ничто иное, как корни XML-документа, т.е.:


$in = [
    [
        'tag' => 'FirstRoot',
    ],
    [
        'tag' => 'SecondRoot',
        'content' => 'Content of SecondRoot',
    ],
];

$request = (new \bupy7\xml\constructor\XmlConstructor())->fromArray($in)->toOutput();

Результат:


<?xml version="1.0" encoding="UTF-8"?>
<FirstRoot/>
<SecondRoot>Content of SecondRoot</SecondRoot>

Конфигурация


Из конфигурации все только самое необходимое.


  • indentString — произвольная строка для отступов. По умолчанию 4 пробела. Если не хотите использовать отступы вообще — передайте false.
  • startDocument — массив ключ-значение с атрибутами XML декларации документа. По умолчанию это <?xml version="1.0" encoding="UTF-8"?>. Если вам не нужна декларация — передайте false.

Для применения конфигурации нужно передать массив ключ-значение в конструктор первым аргументом:


$date = '2016-09-25T12:45:10';
$account = 'f62dcb094cc91617def72d9c260b4483';
$secure = '81ad561784277fa864bf644d755fb164';
$count = 1;
$copy = 4;
$dispatchNumber = '1033229706';
$orderDate = '2016-09-25T12:45:10';

$in = [
    [
        'tag' => 'OrdersPrint',
        'attributes' => [
            'Date' => $date,
            'Account' => $account,
            'Secure' => $secure,
            'OrderCount' => $count,
            'CopyCount' => $copy,
        ],
        'elements' => [
            [
                'tag' => 'Order',
                'attributes' => [
                    'DispathNumber' => $dispatchNumber,
                    'Date' => $orderDate,
                ],
            ],
        ],
    ],
];
$request = (new \bupy7\xml\constructor\XmlConstructor([
        'indentString' => '****',
        'startDocument' => false,
    ]))
    ->fromArray($in)
    ->toOutput();

Результат:


<OrdersPrint Date="2016-09-25T12:45:10" Account="f62dcb094cc91617def72d9c260b4483" Secure="81ad561784277fa864bf644d755fb164" OrderCount="1" CopyCount="4"><Order DispathNumber="1033229706" Date="2016-09-25T12:45:10"/></OrdersPrint>

Заключение


Расширение очень простое и привносит массу удобств во время интеграции с сервисами использующими XML для своего API. Стоит ли использовать xml-constructor — решать только вам.


Спасибо за потраченное на прочтение время!


Ссылки


Tags:
Hubs:
+9
Comments 12
Comments Comments 12

Articles