Александр Некрасов · Автономное руководство

PHP: полный мануал с примерами, функциями и ООП

Разделы этой страницы
  1. XSL: преобразование XML, отчёты и полный справочник XSLTProcessor
  2. Данные отдельно, правила отдельно: catalog.xml → отчёт
  3. Установка, libxslt и версия языка преобразования
  4. Разобрать минимальный XSL по частям
  5. Тот же каталог → HTML-список остатков
  6. Полный пример: два файла данных и правил, затем PHP-запуск
  7. Почему дерево есть, а нужный текст не находится
  8. Все свойства XSLTProcessor и время применения настроек
  9. Все константы XSL: версии, клонирование и запреты
  10. Где возникла ошибка: XML, шаблон, обработчик или запись результата
  11. Когда преобразование становится дорогим
  12. Все методы XSLTProcessor
  13. Функции и методы этой темы
  14. Код и руководство с собой
PHP 8.5.11149 глав4458 карточек справочника
Дерево руководства

Глава 118 · От основ к практике

XSL: преобразование XML, отчёты и полный справочник XSLTProcessor

Все методы XSLTProcessor, параметры, callbacks, свойства, ограничения, профилирование и примеры XML → отчёт.

Данные отдельно, правила отдельно: catalog.xml → отчёт

Наглядная модель

Одни данные товара — отдельные правила отчёта

PHP связывает XML-каталог и XSL-шаблон. До transform они только подготовлены.

  1. $xml: product A → Tea

    DOMDocument разбирает каталог; название Tea находится в элементе title первого товара.

  2. $xsl: catalog/product[1]/title

    Правило xsl:value-of выбирает название. xsl:output method=text задаёт текстовый результат.

  3. $processor->transformToXml($xml)

    Процессор применяет ранее импортированные правила к переданному дереву данных.

  4. $result = 'Tea'

    Строка возвращается в место вызова. Теперь PHP может вывести её или сохранить в отчёт.

XSLT строит результат по правилам; он не запускает PHP-файл из каталога. Для большого XML учитывают память исходного и результирующего деревьев.

Магазин получает один XML-каталог, а отделам нужны разные представления: список остатков, HTML-отчёт, XML другого формата. XSLT позволяет менять правила представления отдельно от данных. Сначала извлечём одно название Desk из реального файла; затем тот же каталог превратится в список товаров в report.html.

Исходный файл catalog.xml
<catalog>
  <product sku="A1"><name>Desk</name><stock>3</stock></product>
</catalog>
Исходный файл first.xsl
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="text" encoding="UTF-8"/>
  <xsl:template match="/">
    <xsl:value-of select="catalog/product[1]/name"/>
  </xsl:template>
</xsl:stylesheet>
Что делает каждый участник
  1. catalog.xml: product/name содержит Desk
  2. first.xsl: правило / выбирает catalog/product[1]/name
  3. DOMDocument разбирает оба XML-файла
  4. XSLTProcessor импортирует правила и применяет к данным
  5. transformToXml возвращает строку Desk
Часть XSLСмысл
xsl:stylesheet и xmlns:xslКорень документа правил; URI связывает префикс xsl с языком XSLT. version="1.0" — версия языка правил, не PHP.
xsl:output method="text"Результат будет обычным текстом. Для будущего отчёта выберем html, для другого XML — xml.
xsl:template match="/"Правило начинается с целого документа. / — корневой узел документа, а не папка на диске.
xsl:value-of select="catalog/product[1]/name"XPath выбирает первый product и его name. Позиции XPath начинаются с 1. value-of выводит текст выбранного значения.
Оба файла превращаются в название первого товараСамостоятельный пример
<?php
declare(strict_types=1);

$data = new DOMDocument();
$data->load(__DIR__ . '/catalog.xml');
$rules = new DOMDocument();
$rules->load(__DIR__ . '/first.xsl');
$processor = new XSLTProcessor();
$processor->importStylesheet($rules);
echo $processor->transformToXml($data), "\n";
Ожидаемый результат
Desk

Оба файла входят в архив и заранее корректны. new DOMDocument создаёт дерево, load читает файл; new XSLTProcessor создаёт исполнитель. importStylesheet загружает правила, а transformToXml отдельно применяет их к $data. Стрелка вызывает действие объекта; скобки передают вход; echo выводит возвращённую строку. Обработка отказов добавлена в полном примере ниже.

XSLT полезен, когда XML-контракт уже существует и нужны отдельные правила преобразования. Если есть PHP-массив и обычный HTML-шаблон, создавать промежуточный XML только ради XSL часто не нужно. Это другой язык, не CSS и не PHP. Он работает с XML-документами, элементами, атрибутами и текстом, которые вводятся в начале XML-маршрута.

Установка, libxslt и версия языка преобразования

Расширению xsl нужны libxml и libxslt версии не ниже 1.1.0. При сборке PHP используют --with-xsl[=DIR]; готовые пакеты устанавливают и включают по правилам ОС/дистрибутива. CLI и PHP-FPM могут читать разные ini: проверяйте тот процесс, который выполняет отчёт. В Windows DLL должна соответствовать сборке PHP по версии, архитектуре и thread safety.

Проверить расширение без выполнения внешних запросовСамостоятельный пример
<?php
declare(strict_types=1);

var_dump(extension_loaded('xsl'), class_exists(XSLTProcessor::class));
echo LIBXSLT_DOTTED_VERSION, "\n";
Ожидаемый результат
bool(true)
bool(true)
1.1.43

Версия 1.1.43 — результат проверенного стенда PHP 8.5.11, а не требование точного совпадения для каждого сервера. При отсутствии модуля константу читать нельзя; сначала проверяют extension_loaded.

libxslt реализует XSLT 1.0 с рядом расширений EXSLT. Запись version="2.0" или "3.0" в шаблоне не добавляет поддержку этих стандартов. Если контракт действительно требует другой версии XSLT, выбирают отдельный совместимый процессор и явно организуют его запуск/интеграцию. XSLTProcessor::hasExsltSupport проверяет библиотеку расширений, не новую версию XSLT.

Разобрать минимальный XSL по частям

ЗаписьЧто происходит
xsl:stylesheet version="1.0"Корень документа правил. Префикс xsl связан с URI http://www.w3.org/1999/XSL/Transform.
xsl:output method="text"Вывод — текст; возможны также xml и html. encoding выбирает байты результата.
xsl:template match="/"Правило для корневого узла документа, с которого начинается обработка.
xsl:value-of select="catalog/product[1]/title"Взять текст title первого product. В XPath позиции начинаются с 1.
xsl:for-each select="catalog/product"Выполнить вложенные инструкции для каждого выбранного товара.
@skuАтрибут sku текущего товара. title — его дочерний элемент.
xsl:textЯвно записать пробел/разделитель/перевод строки. Форматирование самого файла XSL не всегда равно желаемому выводу.
xsl:apply-templatesПередать выбранные узлы соответствующим шаблонам; это другой способ организовать обработку вместо одного большого for-each.
xsl:param name="heading"Объявить настройку, которую можно передать из PHP. Значение читается как $heading в XPath.

В XML символ & в исходном тексте записывается как &amp;, а < внутри текста — как &lt;. PHP-строка, XML-атрибут и XPath-выражение имеют разные правила кавычек. Nowdoc <<<'XSL' хранит шаблон без интерполяции PHP-переменных, поэтому $heading в нём остаётся именем параметра XSL. Сначала разберите строки и структуру XML, если эти уровни пока смешиваются.

Тот же каталог → HTML-список остатков

Теперь результат нужен человеку: заголовок «Склад на сегодня», ниже два товара и их остатки. Изменим только XSL-правила и параметр heading. Данные в catalog.xml останутся теми же.

Исходный файл report.xsl
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="html" encoding="UTF-8" indent="no"/>
  <xsl:param name="heading" select="'Остатки'"/>
  <xsl:template match="/">
    <section><h1><xsl:value-of select="$heading"/></h1><ul>
      <xsl:for-each select="catalog/product">
        <li><xsl:value-of select="@sku"/><xsl:text>: </xsl:text><xsl:value-of select="name"/><xsl:text> — </xsl:text><xsl:value-of select="stock"/></li>
      </xsl:for-each>
    </ul></section>
  </xsl:template>
</xsl:stylesheet>

xsl:for-each select="catalog/product" повторяет вложенную запись li для каждого товара. Внутри этого повтора @sku — атрибут текущего product, name и stock — его дочерние элементы. xsl:text задаёт точные пробелы и разделители. xsl:param объявляет заголовок, а $heading в XSL читает параметр, переданный из PHP.

Сохранить готовый фрагмент HTML в report.htmlСамостоятельный пример
<?php
declare(strict_types=1);

$data = new DOMDocument();
$rules = new DOMDocument();
if (!$data->load(__DIR__ . '/catalog.xml', LIBXML_NONET)
    || !$rules->load(__DIR__ . '/report.xsl', LIBXML_NONET)) {
    throw new RuntimeException('Исходные файлы не прочитаны');
}
$processor = new XSLTProcessor();
$processor->setSecurityPrefs(XSL_SECPREF_DEFAULT | XSL_SECPREF_READ_FILE | XSL_SECPREF_READ_NETWORK);
if (!$processor->importStylesheet($rules)) { throw new RuntimeException('Шаблон не принят'); }
$processor->setParameter('', 'heading', 'Склад на сегодня');
if ($processor->transformToUri($data, __DIR__ . '/report.html') < 0) {
    throw new RuntimeException('Отчёт не сохранён');
}
echo file_get_contents(__DIR__ . '/report.html');
Ожидаемый результат
<section><h1>Склад на сегодня</h1><ul><li>A1: Desk — 3</li><li>B2: Lamp — 0</li></ul></section>

report.html — готовый HTML-фрагмент, который можно открыть или включить в страницу отчёта. Он сгенерирован серверным XSLT, браузеру не требуется выполнять XSL. В этом наборе файлы известны и исправны; полная граница libxml-диагностики показана в следующем разделе «Полный пример». Запреты transform не заменяют контроль исходных XSL-файлов при импорте.

Поменяйте stock в catalog.xml на 7 — изменится соответствующая строка отчёта. Поменяйте heading через setParameter — изменится заголовок. Чтобы показывать только доступные товары, замените путь в for-each на catalog/product[number(stock) > 0]; Lamp с нулём исчезнет. Такая фильтрация представления не проверяет достоверность складских данных.

Полный пример: два файла данных и правил, затем PHP-запуск

Представьте ежедневный отчёт по каталогу. В catalog.xml лежат данные; в report.xsl — только правила текста; run.php связывает их. Это тот же принцип разделения XML/XSL, что в базовом руководстве, с небольшим прикладным каталогом вместо неявных файлов.

Прочитать оба файла и сформировать список товаровСамостоятельный пример
<?php
declare(strict_types=1);

$previous = libxml_use_internal_errors(true);
libxml_clear_errors();
try {
    $xml = new DOMDocument();
    $xsl = new DOMDocument();
    if (!$xml->load(__DIR__ . '/catalog.xml', LIBXML_NONET)
        || !$xsl->load(__DIR__ . '/report.xsl', LIBXML_NONET)) {
        throw new RuntimeException('Некорректные исходные файлы');
    }
    $processor = new XSLTProcessor();
    $processor->setSecurityPrefs(XSL_SECPREF_DEFAULT | XSL_SECPREF_READ_FILE | XSL_SECPREF_READ_NETWORK);
    if (!$processor->importStylesheet($xsl)) {
        throw new RuntimeException('Некорректный XSL-шаблон');
    }
    $result = $processor->transformToXml($xml);
    if ($result === false) {
        throw new RuntimeException('Не удалось сформировать результат');
    }
    echo $result ?? '';
} finally {
    libxml_clear_errors();
    libxml_use_internal_errors($previous);
}
Ожидаемый результат
A: Tea
B: Coffee

Все файлы примера находятся рядом и предоставлены вместе с кодом. load читает файл, loadXML — уже готовый текст. NONET не означает общий запрет локальных файлов; приложение выбирает доверенные пути и шаблон.

Для HTTP-ответа добавляют подходящий Content-Type до первого echo. Для отправки в другую систему проверяют кодировку и контракт её формата. Внешний пользователь не должен выбирать произвольный путь report.xsl через GET; выбирайте шаблон из собственного списка.

Почему дерево есть, а нужный текст не находится

У поставщика может быть корень <catalog xmlns="urn:shop">. Эти элементы принадлежат пространству urn:shop. В XPath 1.0 простая запись catalog/product обозначает элементы без пространства и их не найдёт. Объявите свой префикс в XSL и используйте его в пути. Префикс не обязан совпадать с префиксом исходного XML; совпадать должен URI.

Найти данные из XML с пространством по умолчаниюСамостоятельный пример
<?php
declare(strict_types=1);

$xml = new DOMDocument();
$xml->loadXML('<catalog xmlns="urn:shop"><product><title>Tea</title></product></catalog>');
$xsl = new DOMDocument();
$xsl->loadXML('<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:s="urn:shop"><xsl:output method="text"/><xsl:template match="/"><xsl:value-of select="s:catalog/s:product/s:title"/></xsl:template></xsl:stylesheet>');
$processor = new XSLTProcessor();
$processor->importStylesheet($xsl);
echo $processor->transformToXml($xml), "\n";
Ожидаемый результат
Tea

URI urn:shop связывает путь с элементами. Удаление xmlns из входных данных ради совпадения меняет их смысл и может скрыть ошибку интеграции.

Пустой вывод также бывает из-за неправильного контекста: внутри for-each текущим узлом становится товар, и путь title относится к нему; абсолютный /catalog/product/title начинается с документа. Сначала проверьте вход, namespace и XPath; не пытайтесь лечить всё htmlspecialchars или перекодировкой.

Все свойства XSLTProcessor и время применения настроек

СвойствоТип и смыслКогда учитывать
doXIncludebool, по умолчанию false. Включает обработку XInclude в контексте преобразования.Дополнительные включения могут читать ресурсы; это не обычный xsl:include. Явное объявление свойства с PHP 8.4.
cloneDocumentbool, по умолчанию false. Просит выполнять преобразование на копии входного дерева.Задайте до importStylesheet: в PHP 8.5.11 там определяется режим копирования. xsl:key может потребовать копию автоматически. Явно типизировано с PHP 8.4.
maxTemplateDepthint, максимальная глубина вложения шаблонов. С PHP 8.4.Начальное значение берётся из библиотеки libxslt при создании объекта. Это не глубина XML-дерева и не лимит секунд.
maxTemplateVarsint, лимит переменных/параметров шаблонов в контексте. С PHP 8.4.Защита от чрезмерного выполнения; не лимит количества полей PHP-массива.
Настроить процессор до импорта шаблонаСамостоятельный пример
<?php
declare(strict_types=1);

$processor = new XSLTProcessor();
$processor->cloneDocument = true;
$processor->doXInclude = false;
$processor->maxTemplateDepth = 1000;
$processor->maxTemplateVars = 5000;
var_dump($processor->cloneDocument, $processor->doXInclude);
echo $processor->maxTemplateDepth, '/', $processor->maxTemplateVars, "\n";
Ожидаемый результат
bool(true)
bool(false)
1000/5000

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

Не потерять прежний лимит при отрицательном значенииСамостоятельный пример
<?php
declare(strict_types=1);

$processor = new XSLTProcessor();
$processor->maxTemplateDepth = 1000;
try {
    $processor->maxTemplateDepth = -1;
} catch (ValueError $error) {
    echo "Отрицательный лимит запрещён\n";
}
echo $processor->maxTemplateDepth, "\n";
Ожидаемый результат
Отрицательный лимит запрещён
1000

PHP 8.5.11 отвергает отрицательное значение и восстанавливает старое. Ноль допускается проверкой свойства, но не следует понимать его как документированную команду «снять все лимиты».

Для maxTemplateDepth/maxTemplateVars запрещены unset и косвенная модификация через ссылку: назначайте новое целое непосредственно свойству. cloneDocument увеличивает потребление памяти, но false не обещает полного отсутствия копий. Не используйте XSL_CLONE_NEVER как булево false: историческая константа имеет значение -1.

Все константы XSL: версии, клонирование и запреты

КонстантаТипНазначение
XSL_CLONE_AUTOintИсторическое имя режима автоматического копирования, значение 0. Современное cloneDocument имеет тип bool; используйте false/true и учитывайте автоматическое копирование для xsl:key.
XSL_CLONE_NEVERintИсторическое значение -1. Не присваивайте его bool-свойству с ожиданием запрета клонирования: -1 приводится к true при слабом приведении и не выражает «никогда».
XSL_CLONE_ALWAYSintИсторическое значение 1. В современном API явно задают cloneDocument = true до импорта шаблона.
LIBXSLT_VERSIONintВерсия libxslt числом, например 10143. Это версия библиотеки, не PHP и не версия XSLT.
LIBXSLT_DOTTED_VERSIONstringЧеловекочитаемая версия libxslt, например 1.1.43.
LIBEXSLT_VERSIONintЧисловая версия EXSLT-библиотеки, если собрана соответствующая поддержка.
LIBEXSLT_DOTTED_VERSIONstringВерсия EXSLT в строковой форме; наличие проверяется defined/hasExsltSupport.
XSL_SECPREF_NONEintСнять все эти ограничения libxslt. Не безопасный режим для чужого шаблона.
XSL_SECPREF_READ_FILEintЗапретить чтение файлов при transform.
XSL_SECPREF_WRITE_FILEintЗапретить запись файлов из XSLT.
XSL_SECPREF_CREATE_DIRECTORYintЗапретить создание каталогов из XSLT.
XSL_SECPREF_READ_NETWORKintЗапретить сетевое чтение из XSLT.
XSL_SECPREF_WRITE_NETWORKintЗапретить сетевую запись из XSLT.
XSL_SECPREF_DEFAULTintWRITE_NETWORK | CREATE_DIRECTORY | WRITE_FILE: стандартный запрет записи. Не включает запрет чтения.

Константы расширения доступны после его загрузки. Не фиксируйте числовые маски в своём коде: имена XSL_SECPREF_* объясняют намерение. Версия библиотеки помогает разбирать отличие двух серверов, но не заменяет проверку конкретной доступной функции. XSLTProcessor::getSecurityPrefs и XSLTProcessor::hasExsltSupport.

Где возникла ошибка: XML, шаблон, обработчик или запись результата

Диагностика по этапам
  1. load/loadXML: разобрать XML
  2. importStylesheet: скомпилировать XSL
  3. transform: выполнить XPath и callbacks
  4. Сериализовать/записать результат

libxml_use_internal_errors(true) позволяет забрать диагностику через libxml_get_errors, не выводя предупреждения посетителю. Очистите старый буфер перед операцией, после неё сохраните нужные сообщения в журнал и восстановите предыдущий режим в finally. Ошибки PHP-callback и неверные типы аргументов могут быть исключениями, а не только элементами массива libxml.

СимптомЧто проверить
loadXML вернул true, importStylesheet falseКорректность XPath, namespace XSLT, допустимость инструкций верхнего уровня.
Нет шаблона у процессораНе игнорировали ли false при импорте; не вызвали ли transform раньше импорта.
Пустой результат/nullВыбор узлов, контекст XPath, пространства имён, намеренно пустой шаблон.
Запрет document()Нужен ли дополнительный ресурс; адрес, базовый URI и политика XSL_SECPREF.
Непонятный текст из .phpЧтение файла не запускает PHP через веб-сервер. Подготовьте данные явно, а не подставляйте путь к скрипту.
transformToUri вернул -1Права/путь/ошибка преобразования; 0 байтов сам по себе не ошибка.

Сообщения, номера строк и пути предназначены разработчику. Клиенту отдавайте понятную ошибку с идентификатором операции. Не используйте @, чтобы сделать отчёт «рабочим»: оно скрывает предупреждение, но не исправляет результат.

Когда преобразование становится дорогим

Основные расходы: построение исходного дерева, компиляция XSL, обходы XPath, дерево результата и его сериализация. Повторный глобальный поиск //product внутри обработки каждого товара способен многократно обходить документ. Для повторного поиска по ключу изучают xsl:key/key(), но индекс и автоматическая копия также стоят памяти. Сначала измеряют реальную задачу, затем выбирают алгоритм.

Повторно используйте скомпилированный шаблон, если правила и границы доверия одинаковы; сбрасывайте параметры прошлого задания. XSLTProcessor::setProfiling показывает затраты шаблонов. Не вызывайте БД или HTTP внутри callback для каждого узла: заранее загрузите справочник и работайте с ним в памяти в разумных пределах.

ЗадачаПодход
Известный XML → другое представлениеXSLT отделяет правила преобразования от данных.
Огромный файл → одна запись за разXMLReader; при необходимости преобразовывать отдельные независимые фрагменты.
Массив → простой XML-экспортXMLWriter без дерева всех результатов.
Небольшой XML → простые значенияSimpleXML или DOM могут быть проще отдельного XSL.

Вход и шаблон могут быть маленькими, а результат очень большим из-за циклов/рекурсии. Ограничивайте размер входа, время задания, память и объём вывода на уровне рабочего процесса. XSL_SECPREF не ограничивает вычислительную сложность.

Все методы XSLTProcessor

МетодДля чего нужен
XSLTProcessor::__constructСоздать отдельный процессор с настройками для преобразования XML.
XSLTProcessor::importStylesheetСкомпилировать XSL-шаблон перед преобразованием одного или нескольких XML-документов.
XSLTProcessor::transformToXmlПолучить готовый результат строкой: XML, HTML или текст в зависимости от xsl:output.
XSLTProcessor::transformToDocПолучить дерево результата, чтобы продолжить работу с узлами вместо повторного разбора строки.
XSLTProcessor::transformToUriСформировать выгрузку сразу в выбранный приложением файл и получить число записанных байтов.
XSLTProcessor::setParameterПередать заголовок, язык или другой параметр из PHP в доверенный XSL-шаблон.
XSLTProcessor::getParameterПрочитать установленное PHP-кодом значение параметра процессора.
XSLTProcessor::removeParameterУдалить внешнее значение и снова дать шаблону использовать собственное значение по умолчанию.
XSLTProcessor::getSecurityPrefsУзнать, какие чтения и записи запрещены при выполнении XSLT.
XSLTProcessor::setSecurityPrefsУстановить запреты на дополнительные файловые и сетевые операции внутри преобразования.
XSLTProcessor::hasExsltSupportПроверить, собрана ли поддержка библиотеки расширений EXSLT.
XSLTProcessor::setProfilingЗаписать профиль выполнения XSL-шаблонов, чтобы найти дорогие участки преобразования.
XSLTProcessor::registerPHPFunctionsРазрешить шаблону вызывать конкретные PHP-функции через пространство http://php.net/xsl.
XSLTProcessor::registerPHPFunctionNSДать XSL понятную прикладную функцию вроде shop:label без перечисления глобальных PHP-имён.

Функции и методы этой темы

Откройте карточку, чтобы увидеть договор функции, примеры, ограничения и связанные инструменты.

XSLTProcessor::__constructСоздать отдельный процессор с настройками для преобразования XML.

PHP 5+ · xsl

new XSLTProcessor()

Создать отдельный процессор с настройками для преобразования XML.

Часть вызоваЧто передать или получить
АргументыКонструктор не принимает параметров.
Результат newОбъект XSLTProcessor. Шаблон, входные данные и результат ещё не созданы.
Жизненный циклИмпортируйте XSL, установите параметры, затем преобразуйте XML. Один объект хранит настройки между вызовами.
Данные проходят через преобразование
  1. new: пустой процессор
  2. importStylesheet: правила
  3. transformToXml: вход → результат
Создать исполнитель для отчёта по одному товаруСамостоятельный пример
<?php
declare(strict_types=1);

$data = new DOMDocument();
$data->load(__DIR__ . '/catalog.xml');
$rules = new DOMDocument();
$rules->load(__DIR__ . '/first.xsl');
$processor = new XSLTProcessor();
$processor->importStylesheet($rules);
echo 'Первый товар в отчёте: ', $processor->transformToXml($data), "\n";
Ожидаемый результат
Первый товар в отчёте: Desk

Конструктор не принимает аргументы и создаёт только процессор. Файлы данных и правил подготовлены отдельно; два следующих вызова импортируют правила и запускают преобразование. Второй пример ниже показывает конкретный Error, если этап импорта пропустить.

Объяснимый отказ, если забыли импортировать шаблонСамостоятельный пример
<?php
declare(strict_types=1);

$xml = new DOMDocument();
$xml->loadXML('<catalog/>');
$processor = new XSLTProcessor();
try {
    $processor->transformToXml($xml);
} catch (Error $error) {
    echo "Сначала импортируйте XSL\n";
}
Ожидаемый результат
Сначала импортируйте XSL

В актуальном PHP отсутствие импортированного шаблона приводит к Error. Это ошибка порядка вызовов, а не повод повторять запрос к поставщику.

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

Связанные инструменты: XSLTProcessor::importStylesheet; XSLTProcessor::removeParameter.

XSLTProcessor::importStylesheetСкомпилировать XSL-шаблон перед преобразованием одного или нескольких XML-документов.

PHP 5+ · xsl

public importStylesheet(object $stylesheet): bool

Скомпилировать XSL-шаблон перед преобразованием одного или нескольких XML-документов.

Часть вызоваЧто передать или получить
stylesheetXML-объект с правилами XSLT: например DOMDocument, SimpleXMLElement; с PHP 8.4 поддерживается также современный Dom\Document.
Возвратtrue: шаблон импортирован; false: компиляция не удалась. Ошибки libxml нужно забрать отдельно.
ОшибкиНеподходящий объект даёт TypeError в PHP 8.4+; ранее ValueError. Успешный loadXML ещё не доказывает корректность XSLT.
Данные проходят через преобразование
  1. XSL-текст
  2. XML-дерево шаблона
  3. Компиляция правил
  4. Процессор готов к transform
Один шаблон извлекает название товараСамостоятельный пример
<?php
declare(strict_types=1);

$xml = new DOMDocument();
$xml->loadXML('<catalog><product sku="A"><title>Tea</title><price>250</price></product><product sku="B"><title>Coffee</title><price>450</price></product></catalog>');
$xsl = new DOMDocument();
$xsl->loadXML(<<<'XSL'
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" ><xsl:output method="text" encoding="UTF-8" omit-xml-declaration="yes"/><xsl:template match="/"><xsl:value-of select="catalog/product[1]/title"/></xsl:template></xsl:stylesheet>
XSL);
$processor = new XSLTProcessor();
$processor->importStylesheet($xsl);
echo $processor->transformToXml($xml), "\n";
Ожидаемый результат
Tea

loadXML понимает текст XML. importStylesheet понимает назначения xsl:template и xsl:value-of. Это разные этапы; разделяйте их ошибки.

Отклонить ошибочное правило до формирования отчётаСамостоятельный пример
<?php
declare(strict_types=1);

$previous = libxml_use_internal_errors(true);
libxml_clear_errors();
try {
    $rules = new DOMDocument();
    if ($rules->loadXML('<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"><xsl:template match="/"><xsl:value-of select="["/></xsl:template></xsl:stylesheet>')) {
        echo "XML шаблона разобран\n";
    }
    $processor = new XSLTProcessor();
    if (!$processor->importStylesheet($rules)) {
        echo "Отчёт не запускаем: незавершённый XPath в select\n";
    }
} finally { libxml_clear_errors(); libxml_use_internal_errors($previous); }
Ожидаемый результат
XML шаблона разобран
Отчёт не запускаем: незавершённый XPath в select

В известном fixture одна [ допустима как текст XML-атрибута, но не как законченное XPath-выражение. Поэтому loadXML может быть успешным, а importStylesheet отказывает. Диагностика libxml содержит подробности; её не превращают в публичный HTML без ограничения/экранирования.

Импортируйте один доверенный шаблон и применяйте к нескольким документам: это убирает повторную компиляцию. При замене файла шаблона переимпортируйте его. Изменение исходного DOM после импорта не следует считать изменением уже скомпилированных правил. Относительные xsl:include/import зависят от базового URI: загрузка строки и загрузка файла имеют разные исходные адреса. Политика setSecurityPrefs для этапа transform не заменяет ограничения чтений при импорте.

Связанные инструменты: XSLTProcessor::__construct; XSLTProcessor::transformToXml; XSLTProcessor::setSecurityPrefs.

XSLTProcessor::transformToXmlПолучить готовый результат строкой: XML, HTML или текст в зависимости от xsl:output.

PHP 5+ · xsl

public transformToXml(object $document): string|null|false

Получить готовый результат строкой: XML, HTML или текст в зависимости от xsl:output.

Часть вызоваЧто передать или получить
documentXML-объект с исходными данными; обычно DOMDocument или другой поддерживаемый XML-объект.
СтрокаСериализованный результат. Название метода не заставляет использовать XML: method="text" выдаёт текст.
nullУспешная сериализация пустого результата может дать null. Не смешивайте с ошибкой false.
false / исключениеfalse при отказе преобразования/сериализации; отсутствие шаблона, неверный XML-объект и ошибки PHP-callback имеют собственные Error/TypeError.
Данные проходят через преобразование
  1. Дерево данных
  2. Правила XSLT
  3. Дерево результата
  4. Сериализованная строка
Превратить товары в строки для отчётаСамостоятельный пример
<?php
declare(strict_types=1);

$xml = new DOMDocument();
$xml->loadXML('<catalog><product sku="A"><title>Tea</title><price>250</price></product><product sku="B"><title>Coffee</title><price>450</price></product></catalog>');
$xsl = new DOMDocument();
$xsl->loadXML(<<<'XSL'
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" ><xsl:output method="text" encoding="UTF-8" omit-xml-declaration="yes"/><xsl:template match="/"><xsl:for-each select="catalog/product"><xsl:value-of select="@sku"/><xsl:text>: </xsl:text><xsl:value-of select="title"/><xsl:text>&#10;</xsl:text></xsl:for-each></xsl:template></xsl:stylesheet>
XSL);
$processor = new XSLTProcessor();
$processor->importStylesheet($xsl);
$result = $processor->transformToXml($xml);
if ($result === false) {
    throw new RuntimeException('Не удалось сформировать отчёт');
}
echo $result ?? '';
Ожидаемый результат
A: Tea
B: Coffee

for-each перебирает выбранные product; @sku — атрибут; xsl:text явно сохраняет разделители и перевод строки. Итог можно вернуть HTTP-ответом или положить в письмо.

Обработать намеренно пустой отчёт отдельно от ошибкиСамостоятельный пример
<?php
declare(strict_types=1);

$xml = new DOMDocument();
$xml->loadXML('<catalog><product sku="A"><title>Tea</title><price>250</price></product><product sku="B"><title>Coffee</title><price>450</price></product></catalog>');
$xsl = new DOMDocument();
$xsl->loadXML(<<<'XSL'
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" ><xsl:output method="text" encoding="UTF-8" omit-xml-declaration="yes"/><xsl:template match="/"></xsl:template></xsl:stylesheet>
XSL);
$processor = new XSLTProcessor();
$processor->importStylesheet($xsl);

    $result = $processor->transformToXml($xml);
    if ($result === false) { echo "Ошибка формирования отчёта\n"; }
    elseif ($result === null || $result === '') { echo "Шаблон не вывел строк: отчёт пуст\n"; }
    else { echo $result; }
Ожидаемый результат
Шаблон не вывел строк: отчёт пуст

Шаблон намеренно пуст: преобразование не равно отказу. Условие if (!$result) смешало бы пустой успешный результат и false. В каталожном отчёте неожиданную пустоту стоит проверять по пути XPath и namespace.

encoding в xsl:output определяет байты результата. Если передаёте HTTP-ответ, Content-Type и charset должны соответствовать им. Метод создаёт строку целиком; большой DOM + дерево результата + строка могут одновременно занимать память. Для простого поточного экспорта рассмотрите XMLWriter, а не автоматическое увеличение memory_limit.

Связанные инструменты: XSLTProcessor::transformToDoc; XSLTProcessor::transformToUri.

XSLTProcessor::transformToDocПолучить дерево результата, чтобы продолжить работу с узлами вместо повторного разбора строки.

PHP 5+ · xsl

public transformToDoc(object $document, ?string $returnClass = null): object|false

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

Часть вызоваЧто передать или получить
documentИсходное XML-дерево.
returnClassНеобязательное имя совместимого класса результата; типичный случай — свой наследник DOMDocument. null использует создание DOM-объекта по умолчанию.
ВозвратОбъект дерева или false при отказе. Несовместимый/неизвестный класс даёт TypeError. Не обещает автоматически тот же класс, что у входа.
Данные проходят через преобразование
  1. XML + XSL
  2. Новое дерево
  3. Чтение/редактирование узлов
  4. Отдельная сериализация при необходимости
Получить дерево сводки и дополнить его перед сохранениемСамостоятельный пример
<?php
declare(strict_types=1);

$xml = new DOMDocument();
$xml->loadXML('<catalog><product sku="A"><title>Tea</title><price>250</price></product><product sku="B"><title>Coffee</title><price>450</price></product></catalog>');
$xsl = new DOMDocument();
$xsl->loadXML(<<<'XSL'
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" ><xsl:output method="xml" encoding="UTF-8" omit-xml-declaration="yes"/><xsl:template match="/"><summary><first><xsl:value-of select="catalog/product[1]/title"/></first></summary></xsl:template></xsl:stylesheet>
XSL);
$processor = new XSLTProcessor();
$processor->importStylesheet($xsl);

    $result = $processor->transformToDoc($xml);
    if (!$result instanceof DOMDocument) { throw new RuntimeException('Нет дерева отчёта'); }
    $result->documentElement->setAttribute('checked', 'yes');
    echo $result->saveXML($result->documentElement), "\n";
Ожидаемый результат
<summary checked="yes"><first>Tea</first></summary>

transformToDoc даёт новое дерево, поэтому сразу меняем его атрибут, без transformToXml→строка→loadXML. saveXML в конце нужен лишь для показа фактической структуры. Отметка checked здесь демонстрирует редактирование, не доказывает реальную складскую проверку.

Вернуть свой класс документа для последующей обработкиСамостоятельный пример
<?php
declare(strict_types=1);

$xml = new DOMDocument();
$xml->loadXML('<catalog><product sku="A"><title>Tea</title><price>250</price></product><product sku="B"><title>Coffee</title><price>450</price></product></catalog>');
$xsl = new DOMDocument();
$xsl->loadXML(<<<'XSL'
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" ><xsl:output method="xml" encoding="UTF-8" omit-xml-declaration="yes"/><xsl:template match="/"><summary>ready</summary></xsl:template></xsl:stylesheet>
XSL);
$processor = new XSLTProcessor();
$processor->importStylesheet($xsl);
class ExportDocument extends DOMDocument
{
    public function status(): string
    {
        return $this->documentElement->textContent;
    }
}
$result = $processor->transformToDoc($xml, ExportDocument::class);
if (!$result instanceof ExportDocument) {
    throw new RuntimeException('Нет документа экспорта');
}
echo $result->status(), "\n";
Ожидаемый результат
ready

::class передаёт имя класса, а не создаёт объект. Пример показывает расширение DOMDocument; не рассчитывайте, что фабрика вызовет ваш пользовательский конструктор и его побочные действия.

Современные Dom\XMLDocument и старый DOMDocument — разные семейства API. Поддержка современных входов с PHP 8.4 не означает взаимозаменяемость всех классов и методов результата. Для переносимого кода явно фиксируйте семейство входа/выхода; пример выше целиком использует DOMDocument. Дерево текстового результата может не иметь обычного documentElement — выбирайте XML-структуру, когда нужны элементы.

Связанные инструменты: XSLTProcessor::transformToXml; XSLTProcessor::importStylesheet.

XSLTProcessor::transformToUriСформировать выгрузку сразу в выбранный приложением файл и получить число записанных байтов.

PHP 5+ · xsl

public transformToUri(object $document, string $uri): int

Сформировать выгрузку сразу в выбранный приложением файл и получить число записанных байтов.

Часть вызоваЧто передать или получить
documentИсходное XML-дерево.
uriНазначение результата: например абсолютный путь в разрешённом каталоге экспорта. Не путь из произвольного запроса клиента.
Возврат PHP 8.5.11Число байтов при успехе, -1 при отказе преобразования/сохранения. 0 может быть успешным пустым результатом. Старый текст справочника с false не соответствует int-сигнатуре и текущей реализации.
ОшибкиНеверный вход/отсутствие шаблона могут бросить исключение; ошибки записи могут сопровождаться предупреждениями.
Данные проходят через преобразование
  1. Дерево входа
  2. Преобразование в памяти
  3. Сохранение в uri
  4. Количество байтов или -1
Сохранить ведомость и показать файл вместо длиныСамостоятельный пример
<?php
declare(strict_types=1);

$xml = new DOMDocument();
$xml->loadXML('<catalog><product sku="A"><title>Tea</title><price>250</price></product><product sku="B"><title>Coffee</title><price>450</price></product></catalog>');
$xsl = new DOMDocument();
$xsl->loadXML(<<<'XSL'
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" ><xsl:output method="text" encoding="UTF-8" omit-xml-declaration="yes"/><xsl:template match="/"><xsl:for-each select="catalog/product"><xsl:value-of select="@sku"/><xsl:text>: </xsl:text><xsl:value-of select="title"/><xsl:text>&#10;</xsl:text></xsl:for-each></xsl:template></xsl:stylesheet>
XSL);
$processor = new XSLTProcessor();
$processor->importStylesheet($xsl);

    $path = __DIR__ . '/export.txt';
    if ($processor->transformToUri($xml, $path) < 0) {
        throw new RuntimeException('Ведомость не сохранена');
    }
    echo file_get_contents($path);
Ожидаемый результат
A: Tea
B: Coffee

Числовой возврат сообщает запись/отказ, содержимое находится в export.txt. Выводим саму ведомость. Файл остаётся в учебной папке; этот метод всё равно строит результат преобразования в памяти libxslt и не является потоковым XMLReader.

Успешный пустой файл — отдельное прикладное решениеСамостоятельный пример
<?php
declare(strict_types=1);

$xml = new DOMDocument();
$xml->loadXML('<catalog><product sku="A"><title>Tea</title><price>250</price></product><product sku="B"><title>Coffee</title><price>450</price></product></catalog>');
$xsl = new DOMDocument();
$xsl->loadXML(<<<'XSL'
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" ><xsl:output method="text" encoding="UTF-8" omit-xml-declaration="yes"/><xsl:template match="/"></xsl:template></xsl:stylesheet>
XSL);
$processor = new XSLTProcessor();
$processor->importStylesheet($xsl);

    $path = __DIR__ . '/empty.txt';
    $written = $processor->transformToUri($xml, $path);
    if ($written < 0) { echo "Ошибка сохранения\n"; }
    elseif ($written === 0) { echo "empty.txt создан пустым: проверьте, ожидается ли пустой отчёт\n"; }
Ожидаемый результат
empty.txt создан пустым: проверьте, ожидается ли пустой отчёт

0 байтов допустим для успешного пустого преобразования. Отказ текущего PHP8.5.11 — отрицательное число. Существование файла не делает его подходящим для бизнес-задачи: наш оператор отдельно решает, почему отчёт не содержит строк.

Метод избегает дополнительной PHP-строки результата, но libxslt всё равно строит результат в памяти: это не потоковая обработка гигабайтного XML. setSecurityPrefs регулирует операции внутри XSLT; он не является разрешением для переданного PHP-кодом выходного пути и не заменяет контроль каталога/прав ОС. Сетевые URI и wrapper зависят от сборки и окружения; эти примеры проверены только с локальным файлом.

Связанные инструменты: XSLTProcessor::transformToXml; XSLTProcessor::transformToDoc; XSLTProcessor::setSecurityPrefs.

XSLTProcessor::setParameterПередать заголовок, язык или другой параметр из PHP в доверенный XSL-шаблон.

PHP 5+ · xsl

public setParameter(string $namespace, string $name, string $value): bool
public setParameter(string $namespace, array $options): bool

Передать заголовок, язык или другой параметр из PHP в доверенный XSL-шаблон.

Часть вызоваЧто передать или получить
namespaceURI пространства параметра; пустая строка для обычного непредикатного имени. С PHP 8.5 значение действительно учитывается; в более ранних версиях игнорировалось.
name / valueИмя объявленного xsl:param и строковое значение. Передавайте PHP-переменную $heading, а не строку '$heading'. Это значение, не исполняемое XPath-выражение.
optionsАльтернативная форма: массив имя => строка для нескольких параметров одного пространства.
Возврат / ошибкиbool; неверные типы TypeError. С PHP 8.4 NUL в строковых аргументах ValueError; в 8.5 при непустом namespace нельзя одновременно задавать имя QName/Clark notation.
Версия кавычекС PHP 8.4 корректно поддерживаются строки одновременно с одинарной и двойной кавычками; старые советы по их замене меняли исходный текст.
Данные проходят через преобразование
  1. PHP $heading
  2. setParameter: сохраняется строка
  3. XSL $heading
  4. value-of выводит результат
Передать понятный заголовок без подстановки XML вручнуюСамостоятельный пример
<?php
declare(strict_types=1);

$xml = new DOMDocument();
$xml->loadXML('<catalog><product sku="A"><title>Tea</title><price>250</price></product><product sku="B"><title>Coffee</title><price>450</price></product></catalog>');
$xsl = new DOMDocument();
$xsl->loadXML(<<<'XSL'
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" ><xsl:output method="text" encoding="UTF-8" omit-xml-declaration="yes"/><xsl:param name="heading" select="'Catalog'"/><xsl:template match="/"><xsl:value-of select="$heading"/></xsl:template></xsl:stylesheet>
XSL);
$processor = new XSLTProcessor();
$processor->importStylesheet($xsl);
$heading = 'Products';
var_dump($processor->setParameter('', 'heading', $heading));
echo $processor->transformToXml($xml), "\n";
Ожидаемый результат
bool(true)
Products

PHP-переменная содержит строку Products. XSL-переменная с тем же именем получает её через явный мост setParameter. Вставка $heading в кавычках как текста не выполнит PHP-код.

Передать несколько настроек отчётаСамостоятельный пример
<?php
declare(strict_types=1);

$xml = new DOMDocument();
$xml->loadXML('<catalog><product sku="A"><title>Tea</title><price>250</price></product><product sku="B"><title>Coffee</title><price>450</price></product></catalog>');
$xsl = new DOMDocument();
$xsl->loadXML(<<<'XSL'
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" ><xsl:output method="text" encoding="UTF-8" omit-xml-declaration="yes"/><xsl:param name="heading" select="'Catalog'"/><xsl:param name="currency" select="'RUB'"/><xsl:template match="/"><xsl:value-of select="$heading"/><xsl:text>: </xsl:text><xsl:value-of select="$currency"/></xsl:template></xsl:stylesheet>
XSL);
$processor = new XSLTProcessor();
$processor->importStylesheet($xsl);
$processor->setParameter('', ['heading' => 'Price list', 'currency' => 'EUR']);
echo $processor->transformToXml($xml), "\n";
Ожидаемый результат
Price list: EUR

Второй аргумент — массив настроек, поэтому третий не передают. Неизвестный шаблону параметр не создаёт новый вывод автоматически: правила должны его использовать.

Сохранить обе разновидности кавычек в PHP 8.4+Самостоятельный пример
<?php
declare(strict_types=1);

$xml = new DOMDocument();
$xml->loadXML('<catalog><product sku="A"><title>Tea</title><price>250</price></product><product sku="B"><title>Coffee</title><price>450</price></product></catalog>');
$xsl = new DOMDocument();
$xsl->loadXML(<<<'XSL'
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" ><xsl:output method="text" encoding="UTF-8" omit-xml-declaration="yes"/><xsl:param name="heading" select="'Catalog'"/><xsl:template match="/"><xsl:value-of select="$heading"/></xsl:template></xsl:stylesheet>
XSL);
$processor = new XSLTProcessor();
$processor->importStylesheet($xsl);
$processor->setParameter('', 'heading', 'Alice\'s "Tea"');
echo $processor->transformToXml($xml), "\n";
Ожидаемый результат
Alice's "Tea"

Обратная косая черта защищает одинарную кавычку в PHP-литерале. Не нужно заменять реальные кавычки на другие символы или включать disable-output-escaping.

Передать параметр с пространством имён в PHP 8.5Самостоятельный пример
<?php
declare(strict_types=1);

$xml = new DOMDocument();
$xml->loadXML('<catalog><product sku="A"><title>Tea</title><price>250</price></product><product sku="B"><title>Coffee</title><price>450</price></product></catalog>');
$xsl = new DOMDocument();
$xsl->loadXML(<<<'XSL'
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:report="urn:report"><xsl:output method="text" encoding="UTF-8" omit-xml-declaration="yes"/><xsl:param name="report:heading" select="'Default'"/><xsl:template match="/"><xsl:value-of select="$report:heading"/></xsl:template></xsl:stylesheet>
XSL);
$processor = new XSLTProcessor();
$processor->importStylesheet($xsl);
$processor->setParameter('urn:report', 'heading', 'Namespaced title');
echo $processor->transformToXml($xml), "\n";
Ожидаемый результат
Namespaced title

PHP передаёт URI urn:report и локальное имя heading; префикс report объявлен в XSL и может быть другим. Этот контракт namespace требует PHP 8.5, его нельзя переносить на 8.4 без проверки.

Строковый параметр не равен числовому XPath-предикатуСамостоятельный пример
<?php
declare(strict_types=1);

$xml = new DOMDocument();
$xml->loadXML('<catalog><product sku="A"><title>Tea</title><price>250</price></product><product sku="B"><title>Coffee</title><price>450</price></product></catalog>');
$xsl = new DOMDocument();
$xsl->loadXML(<<<'XSL'
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" ><xsl:output method="text" encoding="UTF-8" omit-xml-declaration="yes"/><xsl:param name="position" select="'1'"/><xsl:template match="/"><xsl:value-of select="catalog/product[position() = number($position)]/title"/></xsl:template></xsl:stylesheet>
XSL);
$processor = new XSLTProcessor();
$processor->importStylesheet($xsl);
$processor->setParameter('', 'position', '2');
echo $processor->transformToXml($xml), "\n";
Ожидаемый результат
Coffee

В XPath 1.0 product[$position] при строковом непустом $position проверяет истинность строки и может выбрать все товары. position() = number($position) выражает намерение явно. Внешнее значение позиции ещё надо проверить как допустимое целое.

Не путайте экранирование XML, строковый параметр и доверие к ссылке. value-of экранирует текст при XML/HTML-сериализации, но если параметр используется как href/src или путь document(), приложение отдельно ограничивает допустимые схемы и адреса. Параметры остаются в процессоре между преобразованиями; удаляйте настройки прошлого клиента.

Связанные инструменты: XSLTProcessor::getParameter; XSLTProcessor::removeParameter; XSLTProcessor::transformToXml.

XSLTProcessor::getParameterПрочитать установленное PHP-кодом значение параметра процессора.

PHP 5+ · xsl

public getParameter(string $namespace, string $name): string|false

Прочитать установленное PHP-кодом значение параметра процессора.

Часть вызоваЧто передать или получить
namespace / nameТа же пара URI и имени, что при setParameter. Пустой namespace для обычного параметра. С PHP 8.5 пространство учитывается.
СтрокаПоследнее сохранённое значение; пустая строка также допустима.
falseУ процессора нет такого параметра. Метод не извлекает select/default из xsl:param внутри шаблона.
ОшибкиNUL в аргументе ValueError с PHP 8.4; неоднозначное квалифицированное имя при непустом namespace ValueError с PHP 8.5.
Данные проходят через преобразование
  1. PHP сохранённые параметры
  2. Поиск URI + имя
  3. Строка либо false
Значение XSL по умолчанию и настройка PHP различаютсяСамостоятельный пример
<?php
declare(strict_types=1);

$xml = new DOMDocument();
$xml->loadXML('<catalog><product sku="A"><title>Tea</title><price>250</price></product><product sku="B"><title>Coffee</title><price>450</price></product></catalog>');
$xsl = new DOMDocument();
$xsl->loadXML(<<<'XSL'
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" ><xsl:output method="text" encoding="UTF-8" omit-xml-declaration="yes"/><xsl:param name="heading" select="'Catalog'"/><xsl:template match="/"><xsl:value-of select="$heading"/></xsl:template></xsl:stylesheet>
XSL);
$processor = new XSLTProcessor();
$processor->importStylesheet($xsl);
var_dump($processor->getParameter('', 'heading'));
echo $processor->transformToXml($xml), "\n";
$processor->setParameter('', 'heading', 'Orders');
echo $processor->getParameter('', 'heading'), "\n";
Ожидаемый результат
bool(false)
Catalog
Orders

Шаблон уже знает Catalog, но процессор не имеет внешнего override. getParameter показывает только назначение через PHP; это удобно для журнала конфигурации отчёта.

Отличить намеренно скрытый заголовок от отсутствующей настройкиСамостоятельный пример
<?php
declare(strict_types=1);

$processor = new XSLTProcessor();
$processor->setParameter('', 'heading', '');
if ($processor->getParameter('', 'heading') === '') {
    echo "Заголовок явно оставлен пустым\n";
}
if ($processor->getParameter('', 'missing') === false) {
    echo "Внешняя настройка missing не задана\n";
}
Ожидаемый результат
Заголовок явно оставлен пустым
Внешняя настройка missing не задана

getParameter читает параметры, назначенные PHP, а не select/default внутри XSL. Пустая строка существует как значение; false означает отсутствие. Для возвращения default используют removeParameter, а не пустую строку.

Связанные инструменты: XSLTProcessor::setParameter; XSLTProcessor::removeParameter.

XSLTProcessor::removeParameterУдалить внешнее значение и снова дать шаблону использовать собственное значение по умолчанию.

PHP 5+ · xsl

public removeParameter(string $namespace, string $name): bool

Удалить внешнее значение и снова дать шаблону использовать собственное значение по умолчанию.

Часть вызоваЧто передать или получить
namespace / nameПара URI и имени внешнего параметра; правила имён такие же, как у setParameter.
trueСохранённый параметр удалён.
falseУдалять было нечего. Это не ошибка компиляции и не удаление объявления xsl:param из XSL.
Данные проходят через преобразование
  1. Override из PHP
  2. Удаление записи
  3. Следующий transform использует default XSL
Вернуться к обычному заголовку после специального отчётаСамостоятельный пример
<?php
declare(strict_types=1);

$xml = new DOMDocument();
$xml->loadXML('<catalog><product sku="A"><title>Tea</title><price>250</price></product><product sku="B"><title>Coffee</title><price>450</price></product></catalog>');
$xsl = new DOMDocument();
$xsl->loadXML(<<<'XSL'
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" ><xsl:output method="text" encoding="UTF-8" omit-xml-declaration="yes"/><xsl:param name="heading" select="'Catalog'"/><xsl:template match="/"><xsl:value-of select="$heading"/></xsl:template></xsl:stylesheet>
XSL);
$processor = new XSLTProcessor();
$processor->importStylesheet($xsl);
$processor->setParameter('', 'heading', 'Special');
echo $processor->transformToXml($xml), "\n";
var_dump($processor->removeParameter('', 'heading'));
echo $processor->transformToXml($xml), "\n";
var_dump($processor->removeParameter('', 'heading'));
Ожидаемый результат
Special
bool(true)
Catalog
bool(false)

Повторное удаление возвращает false, потому что первого назначения уже нет. Это удобно в рабочем процессе, где один шаблон служит нескольким экспортам.

Снять параметр и передать пустой — разные операцииСамостоятельный пример
<?php
declare(strict_types=1);

$xml = new DOMDocument();
$xml->loadXML('<catalog><product sku="A"><title>Tea</title><price>250</price></product><product sku="B"><title>Coffee</title><price>450</price></product></catalog>');
$xsl = new DOMDocument();
$xsl->loadXML(<<<'XSL'
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" ><xsl:output method="text" encoding="UTF-8" omit-xml-declaration="yes"/><xsl:param name="heading" select="'Catalog'"/><xsl:template match="/"><xsl:value-of select="$heading"/></xsl:template></xsl:stylesheet>
XSL);
$processor = new XSLTProcessor();
$processor->importStylesheet($xsl);
$processor->setParameter('', 'heading', '');
var_dump($processor->transformToXml($xml));
$processor->removeParameter('', 'heading');
echo $processor->transformToXml($xml), "\n";
Ожидаемый результат
NULL
Catalog

Пустой override просит вывести пустое значение. Удаление возвращает Catalog. Не пытайтесь очистить конфигурацию заменой всех параметров на пустые строки.

Связанные инструменты: XSLTProcessor::getParameter; XSLTProcessor::setParameter.

XSLTProcessor::getSecurityPrefsУзнать, какие чтения и записи запрещены при выполнении XSLT.

PHP 5+ · xsl

public getSecurityPrefs(): int

Узнать, какие чтения и записи запрещены при выполнении XSLT.

Часть вызоваЧто передать или получить
АргументыНет.
РезультатЦелочисленная битовая маска запрещённых действий. Наличие флага означает запрет, а не разрешение.
По умолчаниюXSL_SECPREF_DEFAULT запрещает запись в файлы, создание каталогов и запись в сеть; чтение файла/сети нужно запрещать дополнительно при необходимости.
Данные проходят через преобразование
  1. Процессор
  2. Маска ограничений
  3. Проверка отдельного бита через &
Прочитать конкретные права шаблона перед его использованиемСамостоятельный пример
<?php
declare(strict_types=1);

$processor = new XSLTProcessor();
$mask = $processor->getSecurityPrefs();
echo ($mask & XSL_SECPREF_WRITE_FILE) !== 0 ? "Запись из XSLT запрещена\n" : "Запись из XSLT разрешена\n";
echo ($mask & XSL_SECPREF_READ_FILE) !== 0 ? "Чтение из XSLT запрещено\n" : "Чтение из XSLT не запрещено этой маской\n";
Ожидаемый результат
Запись из XSLT запрещена
Чтение из XSLT не запрещено этой маской

& проверяет отдельный бит маски. Это диагностический первый шаг: по умолчанию запреты записи не означают запрет чтения. Они относятся к transform libxslt, а не ко всем операциям PHP; рабочий пример отказа document() приведён в setter.

Убедиться, что выбран запрет сетевого чтенияСамостоятельный пример
<?php
declare(strict_types=1);

$processor = new XSLTProcessor();
$processor->setSecurityPrefs(XSL_SECPREF_DEFAULT | XSL_SECPREF_READ_NETWORK);
$blocked = ($processor->getSecurityPrefs() & XSL_SECPREF_READ_NETWORK) !== 0;
echo $blocked ? "XSLT network read blocked\n" : "XSLT network read not blocked\n";
Ожидаемый результат
XSLT network read blocked

Это проверка настройки, без реального сетевого запроса. Она не утверждает, что PHP-callback, DOM load или импорт шаблона охватывается той же границей.

Связанные инструменты: XSLTProcessor::setSecurityPrefs; XSLTProcessor::registerPHPFunctions.

XSLTProcessor::setSecurityPrefsУстановить запреты на дополнительные файловые и сетевые операции внутри преобразования.

PHP 5.4+ · xsl

public setSecurityPrefs(int $preferences): int

Установить запреты на дополнительные файловые и сетевые операции внутри преобразования.

Часть вызоваЧто передать или получить
preferencesБиты XSL_SECPREF_* объединяют через |. XSL_SECPREF_NONE равен отсутствию этих ограничений, а не самому строгому режиму.
ВозвратПредыдущая маска, удобная для восстановления в finally; это не bool успеха.
Граница действияПолитика libxslt на этапе transform. Не полноценная песочница PHP, не лимит CPU/памяти, не общий запрет файлов для приложения.
Данные проходят через преобразование
  1. Выбрать запреты
  2. Запомнить прежнюю маску
  3. Запустить transform
  4. Восстановить настройку
Запретить все пять категорий внешних действий XSLTСамостоятельный пример
<?php
declare(strict_types=1);

$processor = new XSLTProcessor();
$denyAll = XSL_SECPREF_READ_FILE | XSL_SECPREF_WRITE_FILE
    | XSL_SECPREF_CREATE_DIRECTORY | XSL_SECPREF_READ_NETWORK
    | XSL_SECPREF_WRITE_NETWORK;
$previous = $processor->setSecurityPrefs($denyAll);
var_dump($previous === XSL_SECPREF_DEFAULT);
var_dump($processor->getSecurityPrefs() === $denyAll);
$processor->setSecurityPrefs($previous);
Ожидаемый результат
bool(true)
bool(true)

Полезно для доверенного шаблона, которому не нужны document() и дополнительные файлы. Сам XML и XSL должны также загружаться с подходящими ограничениями libxml.

Отчёт останавливается, когда пытается читать запрещённый файлСамостоятельный пример
<?php
declare(strict_types=1);

$xml = new DOMDocument();
$xml->loadXML('<catalog><product sku="A"><title>Tea</title><price>250</price></product><product sku="B"><title>Coffee</title><price>450</price></product></catalog>');
$xsl = new DOMDocument();
$xsl->loadXML(<<<'XSL'
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" ><xsl:output method="text" encoding="UTF-8" omit-xml-declaration="yes"/><xsl:template match="/"><xsl:value-of select="document('extra.xml')/extra"/></xsl:template></xsl:stylesheet>
XSL);
$processor = new XSLTProcessor();
$processor->importStylesheet($xsl);

    $processor->setSecurityPrefs(XSL_SECPREF_DEFAULT | XSL_SECPREF_READ_FILE);
    $previous = libxml_use_internal_errors(true);
    libxml_clear_errors();
    try {
        if ($processor->transformToXml($xml) === false) {
            echo "Отчёт отклонён: шаблону запрещено дополнительное чтение\n";
        }
    } finally { libxml_clear_errors(); libxml_use_internal_errors($previous); }
Ожидаемый результат
Отчёт отклонён: шаблону запрещено дополнительное чтение

extra.xml реально находится в учебной папке. Причина отказа — настройка READ_FILE, а не отсутствующий fixture. Запрет трансформации не заменяет контроль xsl:include/import при компиляции и не ограничивает произвольный PHP callback.

Не лечите ошибку внешнего чтения вызовом setSecurityPrefs(XSL_SECPREF_NONE). Сначала выясните, нужен ли этот ресурс, кто управляет его адресом и на каком этапе он читается. xsl:include/import обрабатываются при компиляции шаблона; загружать XML нужно отдельно с политикой libxml. PHP-callback способен обращаться к файлам и сети от имени процесса независимо от этих флагов. Внешние ресурсы libxml.

Связанные инструменты: XSLTProcessor::getSecurityPrefs; XSLTProcessor::importStylesheet; XSLTProcessor::registerPHPFunctionNS.

XSLTProcessor::hasExsltSupportПроверить, собрана ли поддержка библиотеки расширений EXSLT.

PHP 5.0.4+ · xsl

public hasExsltSupport(): bool

Проверить, собрана ли поддержка библиотеки расширений EXSLT.

Часть вызоваЧто передать или получить
АргументыНет.
true / falsetrue — сборка PHP использует библиотеку EXSLT; false — такой поддержки нет. Это не результат конкретного преобразования.
ОграничениеEXSLT расширяет XSLT 1.0 отдельными функциями, но не превращает libxslt в процессор XSLT 2.0/3.0.
Данные проходят через преобразование
  1. Проверить сборку
  2. Выбрать совместимый шаблон
  3. Использовать конкретную EXSLT-функцию
Проверить доступность расширения перед отчётомСамостоятельный пример
<?php
declare(strict_types=1);

$processor = new XSLTProcessor();
echo $processor->hasExsltSupport() ? "EXSLT available\n" : "EXSLT unavailable\n";
Ожидаемый результат
EXSLT available

Ожидаемый результат относится к проверенной сборке PHP 8.5.11. На другом сервере ветка unavailable — допустимый результат проверки, а не поломка кода.

Собрать временную группу товаров и показать её содержимоеСамостоятельный пример
<?php
declare(strict_types=1);

$xml = new DOMDocument();
$xml->loadXML('<catalog><product sku="A"><title>Tea</title><price>250</price></product><product sku="B"><title>Coffee</title><price>450</price></product></catalog>');
$xsl = new DOMDocument();
$xsl->loadXML(<<<'XSL'
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:exsl="http://exslt.org/common"><xsl:output method="text" encoding="UTF-8" omit-xml-declaration="yes"/><xsl:template match="/"><xsl:variable name="temporary"><item>Tea</item><item>Coffee</item></xsl:variable><xsl:for-each select="exsl:node-set($temporary)/item"><xsl:value-of select="."/><xsl:text>&#10;</xsl:text></xsl:for-each></xsl:template></xsl:stylesheet>
XSL);
$processor = new XSLTProcessor();
$processor->importStylesheet($xsl);

    if (!$processor->hasExsltSupport()) { throw new RuntimeException('Этот шаблон требует EXSLT'); }
    echo $processor->transformToXml($xml);
Ожидаемый результат
Tea
Coffee

В XSLT1.0 временный фрагмент результата ещё не node-set для такого обхода. exsl:node-set превращает его в доступный набор узлов; теперь foreach выдаёт названия. Поддержка EXSLT не означает XSLT2/3; конкретную функцию также можно проверить в XSL через function-available.

Связанные инструменты: XSLTProcessor::importStylesheet; XSLTProcessor::transformToXml.

XSLTProcessor::setProfilingЗаписать профиль выполнения XSL-шаблонов, чтобы найти дорогие участки преобразования.

PHP 5.3+ · xsl

public setProfiling(?string $filename): true

Записать профиль выполнения XSL-шаблонов, чтобы найти дорогие участки преобразования.

Часть вызоваЧто передать или получить
filenameПуть файла профиля; null отключает профилирование. Каталог выбирает приложение, файл не публикуют в webroot.
ВозвратВсегда true при принятии настройки. Файл открывается позже во время transform, поэтому это не подтверждение его успешной записи.
ОшибкиNUL в пути даёт ValueError; отсутствие прав/open_basedir могут помешать записи на этапе выполнения. Точные времена зависят от системы.
Данные проходят через преобразование
  1. Настроить имя файла
  2. Выполнить transform
  3. Прочитать статистику шаблонов
  4. Отключить profiling
Увидеть, какой шаблон действительно выполнялсяСамостоятельный пример
<?php
declare(strict_types=1);

$xml = new DOMDocument();
$xml->loadXML('<catalog><product sku="A"><title>Tea</title><price>250</price></product><product sku="B"><title>Coffee</title><price>450</price></product></catalog>');
$xsl = new DOMDocument();
$xsl->loadXML(<<<'XSL'
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" ><xsl:output method="text" encoding="UTF-8" omit-xml-declaration="yes"/><xsl:template match="/"><xsl:value-of select="catalog/product[1]/title"/></xsl:template></xsl:stylesheet>
XSL);
$processor = new XSLTProcessor();
$processor->importStylesheet($xsl);

    $profile = __DIR__ . '/xsl-profile.txt';
    $processor->setProfiling($profile);
    echo 'Результат: ', $processor->transformToXml($xml), "\n";
    $processor->setProfiling(null);
    $text = file_get_contents($profile);
    if ($text === false || !preg_match('/^\s*0\s+\/\s+(\d+)\s+/m', $text, $match)) {
        throw new RuntimeException('Не найдена строка корневого шаблона в профиле');
    }
    echo 'Профиль: шаблон / выполнен ', $match[1], " раз\n";
Ожидаемый результат
Результат: Tea
Профиль: шаблон / выполнен 1 раз

Полный xsl-profile.txt остаётся рядом и содержит также меняющиеся времена. Для точного учебного вывода извлечена стабильная строка имени/числа вызовов из профиля проверенной libxslt. Это доказывает реальное выполнение шаблона, а не только наличие пустого файла. При другой форме профиля используйте сам файл; настройка пути возвращает true до записи.

Отключить профиль до выполнения заданияСамостоятельный пример
<?php
declare(strict_types=1);

$xml = new DOMDocument();
$xml->loadXML('<catalog><product sku="A"><title>Tea</title><price>250</price></product><product sku="B"><title>Coffee</title><price>450</price></product></catalog>');
$xsl = new DOMDocument();
$xsl->loadXML(<<<'XSL'
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" ><xsl:output method="text" encoding="UTF-8" omit-xml-declaration="yes"/><xsl:template match="/"><xsl:value-of select="catalog/product[1]/title"/></xsl:template></xsl:stylesheet>
XSL);
$processor = new XSLTProcessor();
$processor->importStylesheet($xsl);
$profile = __DIR__ . '/disabled-profile.txt';
$processor->setProfiling($profile);
$processor->setProfiling(null);
echo $processor->transformToXml($xml), "\n";
var_dump(is_file($profile));
Ожидаемый результат
Tea
bool(false)

Настройка пути сама не создаёт файл. Профилирование имеет накладные расходы и обычно включается на время измерения конкретного медленного отчёта.

Повторное преобразование с тем же путём может перезаписать файл профиля. Для параллельных заданий нужны отдельные имена. Измеряйте также загрузку XML, компиляцию XSL и сериализацию: профиль шаблонов не описывает весь HTTP-запрос или SQL-подготовку данных.

Связанные инструменты: XSLTProcessor::transformToXml; XSLTProcessor::importStylesheet.

XSLTProcessor::registerPHPFunctionsРазрешить шаблону вызывать конкретные PHP-функции через пространство http://php.net/xsl.

PHP 5.0.4+ · xsl

public registerPHPFunctions(array|string|null $functions = null): void

Разрешить шаблону вызывать конкретные PHP-функции через пространство http://php.net/xsl.

Часть вызоваЧто передать или получить
functions: stringОдно разрешённое имя, например strtoupper.
functions: arrayСписок имён либо, с PHP 8.4, словарь публичное имя => callable. Выбирайте только необходимые обработчики.
functions: nullРазрешает все PHP-функции. Вызов без аргумента имеет именно этот смысл; это широкое разрешение.
Возвратvoid: метод только регистрирует обработчики. Их результат появляется при последующем вызове из XSLT.
Ошибки с PHP 8.4Некорректное имя ValueError; невызываемый обработчик TypeError. Ошибка внутри callback распространяется из transform.
Данные проходят через преобразование
  1. Зарегистрировать разрешённое имя
  2. XSL php:function вызывает обработчик
  3. PHP получает аргументы
  4. Результат возвращается в XPath
Разрешить одну функцию форматированияСамостоятельный пример
<?php
declare(strict_types=1);

$xml = new DOMDocument();
$xml->loadXML('<catalog><product sku="A"><title>Tea</title><price>250</price></product><product sku="B"><title>Coffee</title><price>450</price></product></catalog>');
$xsl = new DOMDocument();
$xsl->loadXML(<<<'XSL'
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:php="http://php.net/xsl"><xsl:output method="text" encoding="UTF-8" omit-xml-declaration="yes"/><xsl:template match="/"><xsl:value-of select="php:function('strtoupper', string(catalog/product[1]/title))"/></xsl:template></xsl:stylesheet>
XSL);
$processor = new XSLTProcessor();
$processor->importStylesheet($xsl);
$processor->registerPHPFunctions('strtoupper');
echo $processor->transformToXml($xml), "\n";
Ожидаемый результат
TEA

В XSL объявлен префикс php. Первый аргумент php:function — разрешённое имя. string(...) преобразует набор узлов в текст до входа в PHP; strtoupper здесь работает с ASCII-названием.

Подключить небольшое правило сервиса под отдельным именем, PHP 8.4+Самостоятельный пример
<?php
declare(strict_types=1);

$xml = new DOMDocument();
$xml->loadXML('<catalog><product sku="A"><title>Tea</title><price>250</price></product><product sku="B"><title>Coffee</title><price>450</price></product></catalog>');
$xsl = new DOMDocument();
$xsl->loadXML(<<<'XSL'
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:php="http://php.net/xsl"><xsl:output method="text" encoding="UTF-8" omit-xml-declaration="yes"/><xsl:template match="/"><xsl:value-of select="php:function('stockLabel', number(catalog/product[1]/price))"/></xsl:template></xsl:stylesheet>
XSL);
$processor = new XSLTProcessor();
$processor->importStylesheet($xsl);
$processor->registerPHPFunctions([
    'stockLabel' => static function (float $price): string {
        return $price >= 400 ? 'premium' : 'regular';
    },
]);
echo $processor->transformToXml($xml), "\n";
Ожидаемый результат
regular

Именем stockLabel шаблон обращается к callable, а не обязательно к глобальной одноимённой PHP-функции. XPath 1.0 передаёт числа как float; поэтому контракт callback должен это учитывать.

php:function передаёт node-set как массив DOM-узлов; явный string(...) подходит, если нужен текст. Вариант php:functionString преобразует узловые аргументы в строковую форму; выбирайте по контракту обработчика. Результаты должны быть совместимы с мостом XPath: число, строка, bool либо поддерживаемый DOM-узел. Произвольный объект приложения не становится XML автоматически.

Регистрация не предназначена для открытия всех PHP-функций недоверенному шаблону. Даже «только чтение XML» через callback может превратиться в запись файла, SQL или сеть. Используйте новый процессор с явным минимальным набором для каждого профиля доверия; не считайте повторную регистрацию пустого массива универсальным сбросом прежних разрешений.

Связанные инструменты: XSLTProcessor::registerPHPFunctionNS; XSLTProcessor::setSecurityPrefs.

XSLTProcessor::registerPHPFunctionNSДать XSL понятную прикладную функцию вроде shop:label без перечисления глобальных PHP-имён.

PHP 8.4+ · xsl

public registerPHPFunctionNS(string $namespaceURI, string $name, callable $callable): void

Дать XSL понятную прикладную функцию вроде shop:label без перечисления глобальных PHP-имён.

Часть вызоваЧто передать или получить
namespaceURIURI собственного пространства, например urn:shop. Пространство http://php.net/xsl зарезервировано и даёт ValueError.
nameЛокальное допустимое имя XML-функции без двоеточия, например label; неверное имя ValueError.
callableФункция, замыкание или метод, вызываемый из XSL. Невызываемое значение TypeError.
Входные значенияNode-set превращается в массив DOM-узлов; number в XPath — float, string — строка. Не предполагайте, что один узел автоматически пришёл строкой.
Возвратvoid при регистрации. Сам callback возвращает значение в выражение XSL; доступно с PHP 8.4.
Данные проходят через преобразование
  1. urn:shop + label → PHP callable
  2. XSL shop:label(...)
  3. Аргументы → тело PHP
  4. return → исходное выражение XSL
Форматировать значение через собственную функциюСамостоятельный пример
<?php
declare(strict_types=1);

$xml = new DOMDocument();
$xml->loadXML('<catalog><product sku="A"><title>Tea</title><price>250</price></product><product sku="B"><title>Coffee</title><price>450</price></product></catalog>');
$xsl = new DOMDocument();
$xsl->loadXML(<<<'XSL'
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:shop="urn:shop"><xsl:output method="text" encoding="UTF-8" omit-xml-declaration="yes"/><xsl:template match="/"><xsl:value-of select="shop:label(string(catalog/product[1]/title))"/></xsl:template></xsl:stylesheet>
XSL);
$processor = new XSLTProcessor();
$processor->importStylesheet($xsl);
$processor->registerPHPFunctionNS('urn:shop', 'label',
    static fn (string $title): string => 'Product: ' . $title
);
echo $processor->transformToXml($xml), "\n";
Ожидаемый результат
Product: Tea

Префикс shop связывается с urn:shop внутри XSL. registerPHPFunctionNS использует URI и локальное label. PHP return возвращает строку в value-of, после чего продолжается тот же шаблон.

Посчитать только товары, соответствующие правилу приложенияСамостоятельный пример
<?php
declare(strict_types=1);

$xml = new DOMDocument();
$xml->loadXML('<catalog><product sku="A"><title>Tea</title><price>250</price></product><product sku="B"><title>Coffee</title><price>450</price></product></catalog>');
$xsl = new DOMDocument();
$xsl->loadXML(<<<'XSL'
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:shop="urn:shop"><xsl:output method="text" encoding="UTF-8" omit-xml-declaration="yes"/><xsl:template match="/"><xsl:value-of select="shop:countExpensive(catalog/product)"/></xsl:template></xsl:stylesheet>
XSL);
$processor = new XSLTProcessor();
$processor->importStylesheet($xsl);
$processor->registerPHPFunctionNS('urn:shop', 'countExpensive',
    static function (array $products): int {
        $count = 0;
        foreach ($products as $product) {
            $price = (int) $product->getElementsByTagName('price')->item(0)->textContent;
            if ($price >= 400) {
                $count++;
            }
        }
        return $count;
    }
);
echo $processor->transformToXml($xml), "\n";
Ожидаемый результат
1

Здесь массив содержит DOMElement, не строки. Форма XML полностью известна, поэтому price есть у каждого товара; для внешнего каталога нужны проверки отсутствия/формата. Простой порог можно выразить XPath; callback полезен, когда требуется уже существующее правило PHP, а не ради усложнения.

Отделить ошибку обработчика от пустого XMLСамостоятельный пример
<?php
declare(strict_types=1);

$xml = new DOMDocument();
$xml->loadXML('<catalog><product sku="A"><title>Tea</title><price>250</price></product><product sku="B"><title>Coffee</title><price>450</price></product></catalog>');
$xsl = new DOMDocument();
$xsl->loadXML(<<<'XSL'
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:shop="urn:shop"><xsl:output method="text" encoding="UTF-8" omit-xml-declaration="yes"/><xsl:template match="/"><xsl:value-of select="shop:fail()"/></xsl:template></xsl:stylesheet>
XSL);
$processor = new XSLTProcessor();
$processor->importStylesheet($xsl);
$processor->registerPHPFunctionNS('urn:shop', 'fail', static function (): string {
    throw new DomainException('Unknown customer');
});
$previous = libxml_use_internal_errors(true);
try {
    $processor->transformToXml($xml);
} catch (DomainException $error) {
    echo $error->getMessage(), "\n";
} finally {
    libxml_clear_errors();
    libxml_use_internal_errors($previous);
}
Ожидаемый результат
Unknown customer

Исключение из PHP не нужно превращать в «товаров нет». Обработчик задания решает, можно ли повторить операцию или надо исправить данные.

Не выполняйте SQL/HTTP для каждой строки шаблона: сто тысяч узлов могут создать сто тысяч запросов. Загрузите нужный справочник заранее и передайте чистый быстрый обработчик. Такой callback проще тестировать, повторять и профилировать. Рекурсивные обращения к тому же процессору также требуют осторожности; разделяйте независимые преобразования.

Связанные инструменты: XSLTProcessor::registerPHPFunctions; XSLTProcessor::setProfiling.

Источники и обсуждения

Код и руководство с собой

Скачайте самостоятельные примеры с исходными данными, инструкциями и ожидаемыми результатами. Примеры HTTP и фрагменты для проекта помечены отдельно: им нужно указанное окружение.

Код не исполняется на сайте. Скачайте нужный пример и запускайте его в своём локальном окружении.

Материалы сверены 2026-10-12. Примеры и объяснения написаны для этого руководства; источники и обсуждения доступны в каждой главе.