콘텐츠로 이동

Event: PSR-14 수명 주기 이벤트 분류 체계

Event 모듈은 PDF 생성의 각 단계에서 타입이 지정된 수명 주기 이벤트를 디스패치합니다. 리스너는 엔진 내부를 수정하지 않고 문서를 관찰하거나 변환합니다. 디스패처는 PSR-14 모델을 따르므로 기존 PSR-14 도구와도 호환됩니다.

Terminal window
composer require nextpdf/core:^3

Event 모듈은 core 패키지에 포함되어 제공됩니다. 추가 종속성은 없습니다. EventInterfaceStoppableEventInterface 타입은 psr/event-dispatcher 패키지를 요구하지 않으면서도 PSR-14 계약을 그대로 반영합니다.

이 모듈은 디스패처, 리스너 공급자, 고정된 수명 주기 이벤트 클래스 집합이라는 세 부분으로 구성됩니다.

EventDispatcherEventInterface 인스턴스를 받습니다. 그에 맞는 리스너를 ListenerProvider에 요청한 다음, 각 리스너를 우선순위 순서로 호출합니다. dispatch() 메서드는 동일한 이벤트 객체를 반환합니다. 따라서 리스너는 엔진이 이벤트에 담은 상태를 읽을 수 있습니다. 또한 엔진이 나중에 읽을 상태를 다시 기록할 수도 있습니다. 이것이 PSR-14 디스패처의 기본 형태입니다.

ListenerProvider는 이벤트 클래스를 우선순위 순서로 정렬된 호출 가능 항목 목록에 매핑합니다. 등록 범위는 인스턴스 단위입니다. 정적 상태는 없습니다. 워커 프로세스는 자체 공급자 인스턴스를 보유할 수 있습니다. 또한 공급자는 이벤트 클래스 계층과 구현 인터페이스를 순회합니다. 따라서 AbstractEvent에 등록된 리스너는 모든 수명 주기 이벤트를 수신합니다. 인터페이스에 등록된 리스너는 해당 인터페이스를 구현하는 모든 이벤트를 수신합니다.

StoppableEventInterface는 전파 제어 기능을 추가합니다. 리스너는 stopPropagation()을 호출할 수 있습니다. 그러면 디스패처는 해당 주기에서 이후 리스너 호출을 중단합니다. 중단 가능한 이벤트는 리스너 체인을 중지할 자체 메서드를 가진 특수한 경우입니다. PSR-14는 중단 가능한 이벤트에 대해 동일한 모델을 설명합니다(PSR-14 psr_14_event#x4). AbstractEventStoppableEventTrait을 통해 이 인터페이스를 구현합니다. 따라서 모든 수명 주기 이벤트는 기본적으로 중단 가능합니다.

디스패처에는 오버헤드가 전혀 없는 빠른 경로가 있습니다. 이벤트 클래스나 그 상위 클래스 중 어디에 리스너가 있는지 확인합니다. 리스너가 없으면 hasListeners()false를 반환하고 dispatch()는 즉시 반환됩니다. 리스너가 없는 문서는 수명 주기 지점마다 부울 검사 한 번의 비용만 부담합니다.

EventAwareDocumentTrait은 통합 지점입니다. 선택적 EventDispatcher를 보유합니다. protected 디스패치 헬퍼를 제공합니다. Document 클래스는 각 수명 주기 지점에서 이러한 헬퍼를 호출합니다. 디스패처가 설정되지 않으면 모든 헬퍼는 아무 동작도 하지 않습니다.

수명 주기 이벤트 분류 체계:

이벤트네임스페이스발생 시점
DocumentCreatedEventEvent\Document문서가 완전히 구성된 후
PageAddedEventEvent\Document페이지가 초기화된 후
ContentRenderedEventEvent\ContentHTML 또는 텍스트 콘텐츠가 페이지에 렌더링된 후
FontLoadedEventEvent\Content글꼴이 파싱되어 레지스트리에 등록될 때
EncryptionAppliedEventEvent\Security암호화 매개변수가 구성된 후
SignatureAppliedEventEvent\Security서명이 임베드된 후
PdfSerializedEventEvent\Writer직렬화 후, 출력 전달 전
DocumentOutputEventEvent\DocumentPDF 바이트가 대상으로 전달되기 전
심볼종류주요 멤버
NextPDF\Event\EventInterfaceinterfacegetEventName(): non-empty-string
NextPDF\Event\StoppableEventInterfaceinterfaceisPropagationStopped(): bool
NextPDF\Event\StoppableEventTraittraitisPropagationStopped(), stopPropagation()
NextPDF\Event\AbstractEventabstract classgetEventName(); 구현: StoppableEventInterface
NextPDF\Event\EventDispatcherfinal classdispatch(EventInterface): EventInterface, getListenerProvider()
NextPDF\Event\ListenerProviderfinal classaddListener(), getListenersForEvent(), hasListeners(), getListenerCount(), clearListeners()
NextPDF\Event\EventAwareDocumentTraittraitsetEventDispatcher(), getEventDispatcher()
NextPDF\Event\Document\DocumentCreatedEventfinal class$document, $config
NextPDF\Event\Document\PageAddedEventfinal class$document, $pageIndex, $pageSize, $orientation
NextPDF\Event\Document\DocumentOutputEventfinal classgetPdfData(), setPdfData(), getByteSize(), $filename, $destination
NextPDF\Event\Content\ContentRenderedEventfinal class$document, $pageIndex, $contentType, $content
NextPDF\Event\Content\FontLoadedEventfinal class$family, $style, $fontType, $filePath
NextPDF\Event\Security\EncryptionAppliedEventfinal class$document, $algorithm, $allowPrint, $allowCopy, $allowModify
NextPDF\Event\Security\SignatureAppliedEventfinal class$document, $signatureLevel, $signerName, $reason, $location
NextPDF\Event\Writer\PdfSerializedEventfinal class$byteSize, $objectCount, $pageCount, $pdfVersion, $isLinearized, $isEncrypted

addListener()는 이벤트 클래스 문자열이 비어 있으면 NextPDF\Exception\InvalidConfigException을 throw 합니다.

리스너를 등록하고 수명 주기 이벤트를 디스패치합니다.

<?php
declare(strict_types=1);
use NextPDF\Event\Document\PageAddedEvent;
use NextPDF\Event\EventDispatcher;
use NextPDF\Event\ListenerProvider;
$provider = new ListenerProvider();
$provider->addListener(
PageAddedEvent::class,
static function (PageAddedEvent $event): void {
\printf("Page %d added (%s)\n", $event->pageIndex, $event->pageSize->name);
},
);
$dispatcher = new EventDispatcher($provider);

이때 Document 클래스는 디스패처가 setEventDispatcher()를 통해 연결되면 내부적으로 디스패치 헬퍼를 호출합니다.

디스패처를 문서에 연결하고, 리스너 우선순위를 사용하며, 게이트 리스너에서 전파를 중단하는 예입니다.

<?php
declare(strict_types=1);
use NextPDF\Event\Document\DocumentOutputEvent;
use NextPDF\Event\Document\PageAddedEvent;
use NextPDF\Event\EventDispatcher;
use NextPDF\Event\ListenerProvider;
$provider = new ListenerProvider();
// Higher priority runs first. A licensing gate observes every page.
$provider->addListener(
PageAddedEvent::class,
static function (PageAddedEvent $event): void {
if ($event->pageIndex >= 100) {
// Stop later page listeners for this dispatch cycle.
$event->stopPropagation();
}
},
priority: 100,
);
// Last-chance hook: replace the PDF bytes before delivery.
$provider->addListener(
DocumentOutputEvent::class,
static function (DocumentOutputEvent $event): void {
$optimized = \gzencode($event->getPdfData(), 0);
if ($optimized !== false) {
$event->setPdfData($optimized);
}
},
);
$dispatcher = new EventDispatcher($provider);
// Pass $dispatcher to a Document via setEventDispatcher($dispatcher).
  • 와일드카드 등록은 클래스 계층 구조를 기준으로 동작합니다. 모든 이벤트가 이를 확장하므로, AbstractEvent에 등록된 리스너는 모든 수명 주기 이벤트를 수신합니다. 하나의 이벤트만 원하는 경우 리스너를 구체 클래스로 한정하십시오.
  • 리스너 우선순위는 높은 값부터 정렬됩니다. 우선순위가 같으면 삽입 순서가 유지됩니다(안정 정렬).
  • stopPropagation()은 현재 디스패치 주기만 중단합니다. 다음에 디스패치되는 이벤트는 새로운 주기를 시작합니다.
  • 상위 클래스나 인터페이스에 새로 등록하면 여러 이벤트 클래스의 해석이 바뀔 수 있으므로, 정렬된 리스너 캐시는 모든 addListener() 호출 시 무효화됩니다.
  • $document는 문서 범위 이벤트에서 object 타입으로 지정되며, Document 클래스가 아닙니다. 이는 Event 모듈이 강한 Core 종속성에 묶이지 않도록 하기 위함입니다.
  • DocumentOutputEvent::setPdfData()는 비어 있지 않은 문자열을 요구합니다. 페이로드를 빈 문자열로 교체하면 유효하지 않은 문서가 생성됩니다.
  • 또한 EventAwareDocumentTrait의 디스패치 헬퍼는 디스패처가 설정되기 전까지 아무 동작도 하지 않으므로, 리스너 없는 실행에는 측정 가능한 비용이 추가되지 않습니다.

리스너가 없는 디스패치는 리프 이벤트 클래스에 대해 O(1)입니다. 즉, hasListeners() 부울 검사 한 번 후 즉시 반환합니다. 리스너가 있으면 getListenersForEvent()가 이벤트 상위 계층을 한 번 순회하고, 수집된 항목을 정렬하며, 다음 변경이 있을 때까지 이벤트 클래스별로 정렬된 목록을 캐시합니다. 따라서 동일한 클래스를 반복해서 디스패치하는 비용은 일치하는 리스너 k 개에 대해 O(k)입니다. 이 참조 페이지의 기본 performance_budgetwall_ms: 1500, peak_mb: 64입니다.

리스너는 호출자와 동일한 권한으로 생성 파이프라인 내부에서 실행됩니다. 리스너 코드는 신뢰할 수 있는 코드로 취급하십시오. DocumentOutputEvent는 최종 PDF 바이너리를 노출하며 리스너가 이를 교체할 수 있도록 합니다. 감사 리스너나 무결성 리스너는 바이트를 변환하는 모든 리스너보다 먼저 실행되어야 합니다(더 높은 우선순위를 사용하십시오). 보안 범위 이벤트(EncryptionAppliedEvent, SignatureAppliedEvent)는 감사 로깅을 위해 적용된 매개변수를 보고합니다. 이 이벤트는 암호화 결과를 변경하지 않습니다.

사양조항주제
PSR-14 (PHP-FIG)psr_14_event#x4중단 가능한 이벤트가 이후 리스너를 중지함

dispatch() 시그니처, 리스너 공급자 분리, 중단 가능 이벤트 모델은 PSR-14를 따릅니다. NextPDF는 자체 EventInterfaceStoppableEventInterface를 선언합니다. 이 패키지는 덕 타이핑 호환성을 유지하면서 PSR-14 런타임 종속성이 없습니다.

  • /modules/core/contracts/ — 공개 인터페이스 표면
  • /modules/core/observability/ — 텔레메트리 및 메트릭 후크
  • /modules/core/audit/ — 감사 추적 통합
  • /modules/core/config/Config가 전달되는 DocumentCreatedEvent
  • /modules/core/exception/addListener()에서 발생하는 InvalidConfigException

용어집: PSR-14 · 중단 가능한 이벤트 · 리스너 공급자