Триггеры действий и обработчики событий
NextPDF запускает события жизненного цикла через механизм в NextPDF\Event, совместимый с PHP Standards Recommendation 14 (PSR-14). Зарегистрируйте обработчик, выберите приоритет и реагируйте, когда создан документ, добавлена страница, загружен шрифт, выполняется подписание или шифрование, записываются байты или начинается вывод. Останавливайте цепочку только при необходимости.
Установка
Заголовок раздела «Установка»composer require nextpdf/core:^3Концептуальный обзор
Заголовок раздела «Концептуальный обзор»У системы событий две публичные части. ListenerProvider сопоставляет каждый класс события с отсортированным списком вызываемых обработчиков. EventDispatcher проходит по этому списку и вызывает каждый обработчик в порядке приоритета. Оба класса объявлены final, поэтому расширяйте поведение через композицию, а не наследование.
Оба класса совместимы с PSR-14 за счёт утиной типизации. EventDispatcher::dispatch() использует сигнатуру dispatch() из PSR-14 и возвращает событие после того, как отработают все обработчики. ListenerProvider::getListenersForEvent() использует сигнатуру провайдера из PSR-14. NextPDF не требует пакета PSR-14. Если в вашем проекте он установлен, интерфейсы всё равно совпадают.
Для авторов расширений важны два варианта поведения:
- Прослушивание по шаблону. Чтобы разрешить обработчики, провайдер обходит родительские классы и интерфейсы события. Привяжите обработчик к базовому классу
AbstractEvent, чтобы отслеживать каждое событие жизненного цикла. Привяжите обработчик к интерфейсу, чтобы перехватывать всё семейство событий. - Приоритет и распространение. Обработчик с более высоким приоритетом выполняется первым. При равных приоритетах сохраняется порядок регистрации. Каждое событие, наследующее
AbstractEvent, можно остановить. Обработчик может вызватьstopPropagation(), и тогда диспетчер пропустит остальные.
У диспетчера есть быстрый путь без лишних затрат. Когда для класса события или любого его родителя не привязан ни один обработчик, dispatch() возвращается сразу после одной проверки hasListeners().
События жизненного цикла
Заголовок раздела «События жизненного цикла»| Событие | Пространство имён | Срабатывает, когда | Стабильность |
|---|---|---|---|
DocumentCreatedEvent | NextPDF\Event\Document | Завершается построение документа | экспериментальная |
PageAddedEvent | NextPDF\Event\Document | Страница полностью инициализирована | экспериментальная |
ContentRenderedEvent | NextPDF\Event\Content | Содержимое отрисовано на странице | экспериментальная |
FontLoadedEvent | NextPDF\Event\Content | Семейство и начертание шрифта загружаются впервые | экспериментальная |
SignatureAppliedEvent | NextPDF\Event\Security | Внедрены байты подписи | экспериментальная |
EncryptionAppliedEvent | NextPDF\Event\Security | Настроено шифрование | экспериментальная |
PdfSerializedEvent | NextPDF\Event\Writer | Завершается сериализация | экспериментальная |
DocumentOutputEvent | NextPDF\Event\Document | Вот-вот начнётся доставка вывода | экспериментальная |
Диспетчер, провайдер, маркерный интерфейс и базовый класс имеют статус stable (начиная с 3.0.0). Полезные данные событий имеют статус experimental. Их аргументы конструктора и свойства readonly могут измениться в минорном выпуске. Патч-выпуски только добавляют новое. Привязывайтесь к именам свойств полезных данных с учётом этого ограничения.
Поверхность API
Заголовок раздела «Поверхность API»NextPDF\Event\ListenerProvider (стабильный, финальный):
| Метод | Возвращает | Назначение |
|---|---|---|
addListener(string $eventClass, callable $listener, int $priority = 0) | void | Зарегистрировать обработчик; более высокий приоритет выполняется первым. Выбрасывает InvalidConfigException, если класс пуст. |
getListenersForEvent(EventInterface $event) | list<callable> | Разрешить обработчики, включая регистрации на родителях и интерфейсах. |
hasListeners(string $eventClass) | bool | Проверить иерархию классов без накладных расходов. |
getListenerCount(string $eventClass) | int | Подсчитать только прямые регистрации. |
clearListeners() | void | Сбросить провайдер. |
NextPDF\Event\EventDispatcher (стабильный, финальный):
| Метод | Возвращает | Назначение |
|---|---|---|
dispatch(EventInterface $event) | EventInterface | Вызвать обработчики в порядке приоритета, учесть остановку распространения и вернуть событие. |
getListenerProvider() | ListenerProvider | Получить доступ к провайдеру, чтобы добавлять обработчики во время выполнения. |
Документы, запускающие события, используют NextPDF\Event\EventAwareDocumentTrait. Его метод setEventDispatcher() подключает диспетчер к одному документу. Без диспетчера каждый вспомогательный метод диспетчеризации ничего не делает.
Пример кода — быстрый старт
Заголовок раздела «Пример кода — быстрый старт»<?php
declare(strict_types=1);
use NextPDF\Event\EventDispatcher;use NextPDF\Event\ListenerProvider;use NextPDF\Event\Security\SignatureAppliedEvent;
$listeners = new ListenerProvider();$listeners->addListener( SignatureAppliedEvent::class, static function (SignatureAppliedEvent $event): void { \error_log("Signed at level {$event->signatureLevel} by {$event->signerName}"); }, priority: 100,);
$dispatcher = new EventDispatcher($listeners);Пример кода — рабочая среда
Заголовок раздела «Пример кода — рабочая среда»Этот обработчик аудита для рабочей среды использует высокий приоритет, чтобы выполняться первым, пишет структурированные логи и добавляет универсальный обработчик на базовом классе для полноты трассировки.
<?php
declare(strict_types=1);
use NextPDF\Event\AbstractEvent;use NextPDF\Event\EventDispatcher;use NextPDF\Event\ListenerProvider;use NextPDF\Event\Security\EncryptionAppliedEvent;use NextPDF\Event\Security\SignatureAppliedEvent;use Psr\Log\LoggerInterface;
final class SecurityAuditSubscriber{ public function __construct(private readonly LoggerInterface $logger) {}
public function register(ListenerProvider $listeners): EventDispatcher { $listeners->addListener( SignatureAppliedEvent::class, function (SignatureAppliedEvent $event): void { $this->logger->info('signature.applied', [ 'level' => $event->signatureLevel, 'signer' => $event->signerName, ]); }, priority: 1000, );
$listeners->addListener( EncryptionAppliedEvent::class, function (EncryptionAppliedEvent $event): void { $this->logger->info('encryption.applied', [ 'algorithm' => $event->algorithm, ]); }, priority: 1000, );
// Catch-all: observe every lifecycle event for trace completeness. $listeners->addListener( AbstractEvent::class, fn (AbstractEvent $event): mixed => $this->logger->debug('lifecycle', ['event' => $event->getEventName()]), priority: -1000, );
return new EventDispatcher($listeners); }}Граничные случаи и подводные камни
Заголовок раздела «Граничные случаи и подводные камни»- Финальные классы.
EventDispatcherиListenerProviderобъявленыfinal. Используйте их через композицию; не наследуйтесь от них. - Пустой класс события выбрасывает исключение.
addListener('', ...)выбрасываетInvalidConfigException. Всегда передавайте константу class-string. - Стоимость прослушивания по шаблону. Обработчик на
AbstractEventсрабатывает для каждого события. Задавайте универсальным обработчикам низкий приоритет и сохраняйте их недорогими. - Изменение вывода.
DocumentOutputEventнесёт байты Portable Document Format (PDF). Движок считывает их обратно после диспетчеризации. Если вы измените эти байты, вы получите широкий контроль и такой же широкий риск. Неверное смещение байта повреждает PDF и может нарушить подпись. Предпочтительнее наблюдать, если только вы не отвечаете за результат ради детерминизма и подписей. - Нет диспетчера — нет событий. Документ без диспетчера, заданного через
EventAwareDocumentTrait, не запускает события. Это предусмотренный путь без лишних затрат, а не ошибка настройки.
Производительность
Заголовок раздела «Производительность»Быстрый путь — это одна проверка hasListeners() по цепочке родителей. Без обработчиков диспетчеризация почти ничего не стоит. Провайдер кэширует отсортированный список обработчиков для каждого класса события и очищает этот кэш только при изменении обработчиков. Делайте обработчики неблокирующими, потому что они выполняются прямо на пути отрисовки.
Замечания по безопасности
Заголовок раздела «Замечания по безопасности»SignatureAppliedEvent и EncryptionAppliedEvent — это точки привязки аудита. Регистрируйте обработчики с высоким приоритетом, чтобы записывать подписание и шифрование в защищённое от подделки хранилище. Не останавливайте цепочку на событии безопасности, если только вы не намерены заглушить последующие обработчики. Остановка цепочки может незаметно отключить хуки аудита, которые выполняются после.
Соответствие
Заголовок раздела «Соответствие»Эта страница не делает нормативных утверждений сверх совместимости с PSR-14. Эта совместимость основана только на утиной типизации и не требует пакета PSR-14.
Коммерческий контекст
Заголовок раздела «Коммерческий контекст»NextPDF Enterprise поставляет проверенные обработчики для событий подписания и шифрования, которые наполняют защищённый от подделки журнал аудита. Поскольку контракт обработчика — это публичный API событий (application programming interface, API), ваши собственные обработчики могут сосуществовать с обработчиками Enterprise в одном провайдере.
Смотрите также
Заголовок раздела «Смотрите также»- Обзор разработки расширений
- Пользовательские шрифты
- Пользовательский макет и перехват текста
- Контракт провайдера Key Management Service (KMS)
- Правила стабильности Service Provider Interface (SPI)
Связанные контракты и модули
Заголовок раздела «Связанные контракты и модули»- Справочник модуля событий — таксономия событий жизненного цикла PSR-14 и устройство диспетчера.
- Справочник контрактов подписания — контракты, лежащие за
SignatureAppliedEvent. - Правила стабильности SPI — как версионируются стабильный диспетчер и экспериментальные полезные данные.
- Пользовательские шрифты — связывает
FontLoadedEventс контрактом реестра. - Обзор разработки расширений — полная публичная поверхность SPI.
Глоссарий определяет термины event listener, event dispatcher, listener provider и stoppable event, используемые на этой странице. Канонические определения смотрите в опубликованном глоссарии.