액션 트리거와 이벤트 리스너
한눈에 보기
섹션 제목: “한눈에 보기”NextPDF는 NextPDF\Event의 PSR-14 호환 시스템을 통해 수명 주기 이벤트를 발생시킵니다. 우선순위를 지정해 리스너를 등록하고, 문서 생성, 새 페이지, 글꼴 로딩, 서명, 암호화, 기록, 출력에 반응합니다. 필요할 때는 체인을 중단할 수 있습니다.
composer require nextpdf/core:^3개념 개요
섹션 제목: “개념 개요”이벤트 시스템에는 두 가지 공개 구성 요소가 있습니다. ListenerProvider는 이벤트 클래스를 정렬된 리스너 콜러블 목록에 매핑합니다. EventDispatcher는 그 목록을 순회하며 각 리스너를 우선순위 순서로 호출합니다. 두 클래스 모두 final이므로 서브클래싱이 아니라 컴포지션으로 확장하십시오.
두 클래스 모두 덕 타이핑 방식으로 PSR-14에 부합합니다. EventDispatcher::dispatch()는 PSR-14 dispatch() 시그니처에 부합하며, 모든 리스너가 실행된 후 이벤트를 반환합니다. 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 (stable, final):
| 메서드 | 반환값 | 용도 |
|---|---|---|
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 (stable, final):
| 메서드 | 반환값 | 용도 |
|---|---|---|
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); }}엣지 케이스 및 주의 사항
섹션 제목: “엣지 케이스 및 주의 사항”- final 클래스.
EventDispatcher와ListenerProvider는final입니다. 서브클래싱하지 말고 컴포지션하십시오. - 빈 이벤트 클래스는 예외를 던집니다.
addListener('', ...)은InvalidConfigException을 던집니다. 항상 class-string 상수를 전달하십시오. - 와일드카드 비용.
AbstractEvent에 등록된 리스너는 모든 이벤트에 대해 발생합니다. 포괄 리스너에는 낮은 우선순위를 부여하고 가볍게 유지하십시오. - 출력 수정.
DocumentOutputEvent는 PDF 바이트를 전달합니다. 디스패치 후 엔진은 이를 다시 읽습니다. 이러한 바이트를 변경하면 광범위한 제어가 가능하지만 실질적인 위험이 따릅니다. 잘못된 바이트 오프셋은 PDF를 손상시키고 서명을 무효화할 수 있습니다. 결정성과 서명에 미치는 영향을 직접 책임질 수 있는 경우가 아니라면 관찰하는 편을 택하십시오. - 디스패처가 없으면 이벤트도 없음.
EventAwareDocumentTrait을 통해 디스패처가 설정되지 않은 문서는 이벤트를 발생시키지 않습니다. 이는 의도된 비용 없는 경로이며 설정 오류가 아닙니다.
빠른 경로는 한 번의 hasListeners() 상위 클래스 체인 확인입니다. 리스너가 없으면 디스패치는 거의 비용이 들지 않습니다. 프로바이더는 이벤트 클래스별로 정렬된 리스너 목록을 캐시합니다. 변경이 있을 때만 해당 캐시를 비웁니다. 리스너는 논블로킹으로 유지하십시오. 리스너는 렌더링 경로에서 인라인으로 실행됩니다.
보안 참고 사항
섹션 제목: “보안 참고 사항”SignatureAppliedEvent와 EncryptionAppliedEvent는 감사 기준점입니다. 변조 감지 가능한 저장소에 서명과 암호화를 로깅하도록 높은 우선순위의 리스너를 등록하십시오. 후속 리스너를 의도적으로 막으려는 경우가 아니라면 보안 이벤트에서 체인을 중단하지 마십시오. 체인을 중단하면 후속 감사 훅이 조용히 비활성화될 수 있습니다.
적합성
섹션 제목: “적합성”이 페이지에서는 PSR-14 호환성 외에 어떠한 규범적 주장도 하지 않습니다. 해당 호환성은 덕 타입에만 해당하며 PSR-14 패키지를 필요로 하지 않습니다.
상업적 맥락
섹션 제목: “상업적 맥락”NextPDF Enterprise는 변조 감지 가능한 감사 로그에 데이터를 제공하는, 서명 및 암호화 이벤트용으로 감사를 거친 리스너를 제공합니다. 리스너 계약은 공개 이벤트 API이므로 사용자의 리스너는 동일한 프로바이더에서 Enterprise 리스너와 공존합니다.
함께 보기
섹션 제목: “함께 보기”관련 계약 및 모듈
섹션 제목: “관련 계약 및 모듈”- Event 모듈 레퍼런스 — PSR-14 수명 주기 이벤트 분류 체계와 디스패처 내부 동작을 설명합니다.
- 서명 계약 레퍼런스 —
SignatureAppliedEvent뒤에 있는 계약입니다. - SPI 안정성 규칙 — stable 디스패처와 experimental 페이로드의 버전 관리 방식입니다.
- 사용자 정의 글꼴 —
FontLoadedEvent를 레지스트리 계약과 결합합니다. - 확장 작성 개요 — 전체 공개 SPI 표면입니다.
이 페이지에서 사용되는 event listener, event dispatcher, listener provider, stoppable event 용어는 용어집에서 정의합니다. 각 표준 정의는 게시된 용어집을 참조하십시오.