Метаданные: построение пакета XMP и потоковое чтение
Краткий обзор
Заголовок раздела «Краткий обзор»Модуль Metadata — слой Extensible Metadata Platform (XMP) в движке. Он создаёт пакет XMP, который файл Portable Document Format (PDF) хранит как поток метаданных. Он читает существующий пакет, не загружая весь документ в память. Он формирует XMP-расширение движка для журнала аудита.
Установка
Заголовок раздела «Установка»composer require nextpdf/core:^3Концептуальный обзор
Заголовок раздела «Концептуальный обзор»PDF хранит метаданные уровня документа как пакет XMP в потоке метаданных, прикреплённом к каталогу документа, как описано в ISO 32000-2 §14.3. Этот модуль отвечает за создание и чтение такого пакета. Его API намеренно невелик и сфокусирован: три класса в пространстве имён NextPDF\Metadata\Xmp.
XmpMetadataBuilder создаёт пакет. Он сериализует набор свойств в корректно сформированный XMP-документ, обёрнутый в стандартные инструкции обработки <?xpacket?>. Он использует канонический глобально уникальный идентификатор (GUID) пакета и метку порядка байтов, заданные спецификацией XMP. Результат — строка байтов, которую Writer встраивает как поток метаданных, то есть представление XMP внутри PDF, описанное в §14.3.
XmpStreamReader читает пакет. Он рассчитан на недоверенный ввод. Перед разбором источник потоково, фрагментами по 64 КБ, записывается во временный файл с ограниченным размером. Во время этой записи средство чтения контролирует общий лимит по байтам. На время разбора загрузчик сущностей libxml устанавливается в null, а затем восстанавливается. При наличии DOCTYPE происходит жёсткий отказ. iterateProperties() возвращает генератор, который выдаёт кортежи (namespaceUri, localName, textContent) для каждого конечного элемента, не строя всё дерево в памяти; в любой момент в анализаторе хранится только текущий элемент и его текстовый узел. Слишком большой пакет вызывает PacketTooLargeException; некорректный Extensible Markup Language (XML), DOCTYPE или ввод не в UTF-8 вызывает InvalidConfigException.
XmpAuditFieldEmitter — расширение, специфичное для движка. Он отрисовывает AuditReport в пользовательское поле XMP в пространстве имён nextpdfAudit, поэтому аудит соответствия документа перемещается вместе с файлом как стандартизированный XMP, а не как отдельный файл. AuditReport, который он отрисовывает, создаётся не эмиттером. Вызывающий код включает обогащение, запуская отрисовку в режиме CssRenderingMode::Audit с переданным auditCollector, настроенным через Config(auditCollector: ...). Сборщиком управляет вызывающая сторона: она наполняет его, а эмиттер отрисовывает всё, что было собрано. Он новее базовой поверхности XMP (@since 5.4.0). Построитель и средство чтения — @since 2.0.0.
Поверхность API
Заголовок раздела «Поверхность API»| Класс | Ключевые члены | Роль |
|---|---|---|
XmpMetadataBuilder | build(): string, XPACKET_GUID, XPACKET_BOM | Сериализует набор свойств в пакет XMP (@since 2.0.0) |
XmpStreamReader | iterateProperties(mixed $source, int $byteCap = DEFAULT_BYTE_CAP): \Generator, DEFAULT_BYTE_CAP | Потоковое средство чтения XMP с ограничением размера, отклоняющее DOCTYPE (@since 2.0.0) |
PacketTooLargeException | расширяет NextPdfException | Выбрасывается, когда пакет XMP превышает лимит по байтам (@since 2.0.0) |
XmpAuditFieldEmitter | render(?AuditReport $report): string, NAMESPACE_URI | Отрисовывает журнал аудита как пользовательское поле XMP (@since 5.4.0) |
Запустите composer docs:generate-api-php -- --module=Metadata, чтобы сгенерировать полную таблицу PHPDoc.
Пример кода — быстрый старт
Заголовок раздела «Пример кода — быстрый старт»Потоковое извлечение свойств из существующего пакета XMP с явно заданным лимитом в байтах.
<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use NextPDF\Metadata\Xmp\XmpStreamReader;
$reader = new XmpStreamReader();
foreach ($reader->iterateProperties(file_get_contents('/srv/in/xmp.xml'), byteCap: 1_048_576) as [$ns, $name, $value]) { printf("%s:%s = %s\n", $ns, $name, $value);}Пример кода — промышленная эксплуатация
Заголовок раздела «Пример кода — промышленная эксплуатация»Защищённое чтение пакета: типизированные ошибки модуля сопоставляются с результатом уровня приложения, а низкоуровневые сбои анализатора не выходят наружу.
<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use NextPDF\Exception\InvalidConfigException;use NextPDF\Metadata\Xmp\PacketTooLargeException;use NextPDF\Metadata\Xmp\XmpStreamReader;use Psr\Log\LoggerInterface;
final readonly class XmpIngestService{ public function __construct( private XmpStreamReader $reader, private LoggerInterface $logger, ) {}
/** * @param resource|string $source A stream resource or XMP byte string. * * @return array<string, string> Flattened "ns:name" => value map. */ public function ingest(mixed $source): array { $properties = [];
try { // Cap untrusted XMP at 4 MB regardless of the 1 GiB default. foreach ($this->reader->iterateProperties($source, byteCap: 4_194_304) as [$ns, $name, $value]) { $properties["{$ns}:{$name}"] = $value; } } catch (PacketTooLargeException $e) { $this->logger->warning('XMP packet exceeded ingest cap; rejected.', ['error' => $e->getMessage()]);
return []; } catch (InvalidConfigException $e) { $this->logger->warning('XMP packet malformed or unsafe; rejected.', ['error' => $e->getMessage()]);
return []; }
return $properties; }}Краевые случаи и подводные камни
Заголовок раздела «Краевые случаи и подводные камни»XmpStreamReaderсразу отклоняет любой DOCTYPE. Это защита от XML External Entity (XXE), а не косметическая проверка валидации; пакет, которому нужен DOCTYPE, не принимается. Очистите его заранее, на стороне источника.- По умолчанию лимит по байтам составляет 1 ГиБ (
DEFAULT_BYTE_CAP). Это значение по умолчанию — верхний предел, а не рекомендация. Для недоверенного ввода передавайте жёсткийbyteCap. iterateProperties()— это генератор. Пройдите по нему один раз; повторный перебор не воспроизводит данные заново.- На время разбора средство чтения устанавливает загрузчик сущностей libxml в null, а затем восстанавливает его. Не запускайте такое чтение одновременно с другим разбором на основе libxml в том же запросе, если тот разбор зависит от загрузчика сущностей.
XmpAuditFieldEmitter::render(null)допустим и даёт пустой результат отрисовки; null в качествеAuditReportозначает “нет аудита”, а не ошибку.
Производительность
Заголовок раздела «Производительность»Построитель работает линейно по количеству свойств. Потребление памяти средством чтения определяется самым длинным отдельным фрагментом текста, а не размером документа, потому что в анализаторе хранится только текущий элемент; крупные пакеты обрабатываются потоком, а не загружаются в память. Эталонная нагрузка по умолчанию укладывается в бюджет 1500 мс по времени и 64 МБ по пиковой памяти. Профиль воспроизводимости — structural: пакет XMP записывает временные метки изменения. Две сборки одних и тех же логических метаданных различаются в этих полях, тогда как их структура идентична.
Замечания по безопасности
Заголовок раздела «Замечания по безопасности»XmpStreamReader разбирает недоверенный XML и имеет соответствующие защиты. Потоковая разбивка на фрагменты с принудительным лимитом по байтам ограничивает отказ в обслуживании из-за роста потребления памяти. Отклонение DOCTYPE закрывает XXE. LIBXML_NONET блокирует разрешение сетевых сущностей. Ввод не в UTF-8 отвергается. Тем не менее задавайте подходящий для развёртывания byteCap для любого пакета из внешнего источника, а не полагайтесь на гигабайтное значение по умолчанию. Рассматривайте значения свойств XMP как недоверенные строки, когда они снова попадают в приложение. См. модель угроз движка в /modules/core/security/.
Соответствие стандартам
Заголовок раздела «Соответствие стандартам»Пакет, который создаёт XmpMetadataBuilder, — это представление потока метаданных XMP внутри PDF, определённое в ISO 32000-2 §14.3 (). Сама форма сериализации XMP регулируется спецификацией XMP (ISO 16684-1), которая не входит в проверяемый корпус цитирования. На это требование ссылаются по номеру, без привязки к конкретному фрагменту. Эти факты реализации содержатся в src/Metadata/Xmp/ и проверяются в tests/Unit/Metadata/Xmp/. Сквозное соответствие метаданных профилю (PDF/A, PDF/UA) проверяется наборами oracle и golden, описанными в /modules/core/conformance/.
См. также
Заголовок раздела «См. также»- Модуль Document — дерево DPart в паре с Document Part Metadata (DPM).
- Модуль Audit — создаёт
AuditReport, который затем отрисовывает эмиттер. - Модуль Writer — встраивает пакет в виде потока метаданных.
- Обзор соответствия стандартам
- Модель безопасности движка