도큐트린 퍼시스턴스 이벤트 시스템 완벽 가이드: 라이프사이클 이벤트와 커스텀 이벤트 마스터하기
도큐트린 퍼시스턴스(Docctrine Persistence)는 객체 매핑기(Object Mapper)의 지속성(Persistence)을 위한 강력한 공통 추상화 라이브러리입니다. 이 라이브러리의 핵심 구성 요소 중 하나는 이벤트 시스템으로, 데이터 지속화 로직을 유연하게 확장할 수 있게 해줍니다. 이 글에서는 도큐트린 퍼시스턴스의 이벤트 시스템을 심도 있게 탐구하여, 라이프사이클 이벤트의 활용법과 커스텀 이벤트 생성 방법을 배워보겠습니다.
도큐트린 퍼시스턴스 이벤트 시스템이란?
도큐트린 퍼시스턴스의 이벤트 시스템은 객체의 라이프사이클의 주요 순간에 사용자 정의 로직을 실행할 수 있게 해줍니다. 이벤트 메커니즘을 통해 객체가 데이터베이스에 저장, 업데이트 또는 삭제될 때 특정 작업을 자동으로 트리거할 수 있으며, 이는 객체 클래스 자체를 수정할 필요가 없습니다. 이러한 설계는 관찰자 패턴(Observer Pattern)을 따르며, 느슨한 결합(Loose Coupling)을 제공하여 코드를 더 쉽게 유지보수하고 확장할 수 있게 합니다.
핵심 구성 요소: ObjectManager와 이벤트 상호작용
이벤트 시스템의 핵심은 ObjectManager 인터페이스입니다. 이 인터페이스는 객체 지속화의 기본 작업을 정의합니다. persist(), flush() 등의 메서드가 호출될 때, ObjectManager는 해당하는 라이프사이클 이벤트를 자동으로 트리거합니다. 예를 들어:
$objectManager = new ObjectManager();
$product = new Product();
$objectManager->persist($product); // prePersist 이벤트 트리거
$objectManager->flush(); // postPersist 이벤트 트리거
주요 라이프사이클 이벤트 상세 설명
도큐트린 퍼시스턴스는 객체가 생성되어 삭제되는 전체 라이프사이클을 포함하는 풍부한 라이프사이클 이벤트를 제공합니다. 가장 일반적으로 사용되는 이벤트는 다음과 같습니다:
지속화 관련 이벤트
- prePersist: 객체가 데이터베이스에 저장되기 전에 트리거됩니다. 생성 시간戳(타임스탬프) 설정과 같은 초기화 작업에 적합합니다.
- postPersist: 객체가 데이터베이스에 성공적으로 저장된 후에 트리거됩니다. 알림 전송이나 로그 기록과 같은 후속 작업에 적합합니다.
업데이트 관련 이벤트
- preUpdate: 객체가 업데이트되기 전에 트리거됩니다. 데이터 유효성 검사나 업데이트 필드 수정에 사용할 수 있습니다.
- postUpdate: 객체가 업데이트된 후에 트리거됩니다. 캐시 정리와 같은 후속 작업에 적합합니다.
삭제 관련 이벤트
- preRemove: 객체가 삭제되기 전에 트리거됩니다. 데이터 백업이나 권한 확인에 사용할 수 있습니다.
- postRemove: 객체가 삭제된 후에 트리거됩니다. 연관된 리소스 정리에 적합합니다.
로드 관련 이벤트
- postLoad: 객체가 데이터베이스에서 로드된 후에 트리거됩니다. 데이터 후처리에 사용할 수 있습니다.
이벤트 리스너를 어떻게 생성하나요?
이벤트 시스템을 사용하려면 이벤트 리스너를 생성하고 이벤트 관리자(EventManager)에 등록해야 합니다. 이벤트 리스너를 생성하는 기본 단계는 다음과 같습니다:
1. 이벤트 리스너 클래스 정의
특정 이벤트 인터페이스를 구현하는 클래스를 생성합니다. 예를 들어 prePersist와 preUpdate 이벤트를 감시하는 리스너는 다음과 같이 정의할 수 있습니다:
use Doctrine\Persistence\Event\LifecycleEventArgs;
class AuditLogger
{
public function prePersist(LifecycleEventArgs $args): void
{
$entity = $args->getObject();
if ($entity instanceof AuditableInterface) {
$entity->setCreatedAt(new DateTime());
// 감사 로그 기록
$this->logAudit('Entity created: ' . get_class($entity));
}
}
public function preUpdate(LifecycleEventArgs $args): void
{
$entity = $args->getObject();
if ($entity instanceof AuditableInterface) {
$entity->setUpdatedAt(new DateTime());
// 감사 로그 기록
$this->logAudit('Entity updated: ' . get_class($entity));
}
}
private function logAudit(string $message): void
{
// 실제 로깅 로직 구현
error_log($message);
}
}
2. 이벤트 리스너 등록
리스너를 EventManager에 등록합니다:
$eventManager = $objectManager->getEventManager();
$eventManager->addEventListener(
['prePersist', 'preUpdate'],
new AuditLogger()
);
LifecycleEventArgs에 대한 심층 이해
LifecycleEventArgs는 모든 라이프사이클 이벤트의 기본 클래스로, 현재 객체와 ObjectManager에 접근하는 메서드를 제공합니다:
getObject(): 현재 작업 중인 객체를 가져옵니다 (더 이상 사용되지 않는getEntity()메서드의 대안).getObjectManager(): 현재의ObjectManager인스턴스를 가져옵니다.
예를 들어, PreUpdateEventArgs에서는 필드 변경 정보도 얻을 수 있습니다:
public function preUpdate(PreUpdateEventArgs $args): void
{
$entity = $args->getObject();
$changes = $args->getEntityChangeSet();
if (isset($changes['price'])) {
// 가격 변경 로직 처리
$this->processPriceChange($entity, $changes['price'][0], $changes['price'][1]);
}
}
커스텀 이벤트 생성하기
내장된 라이프사이클 이벤트 외에도, 특정 비즈니스 요구사항을 충족하기 위해 커스텀 이벤트를 생성할 수 있습니다.
1. 이벤트 클래스 정의
EventArgs를 상속하는 이벤트 클래스를 생성합니다:
use Doctrine\Common\EventArgs;
class PaymentProcessedEvent extends EventArgs
{
private $payment;
public function __construct(Payment $payment)
{
$this->payment = $payment;
}
public function getPayment(): Payment
{
return $this->payment;
}
}
2. 커스텀 이벤트 트리거
비즈니스 로직에서 이벤트를 트리거합니다:
$eventManager = $objectManager->getEventManager();
$eventManager->dispatchEvent(
'payment.processed',
new PaymentProcessedEvent($payment)
);
3. 이벤트 리스너 생성
커스텀 이벤트에 대한 리스너를 생성합니다:
class EmailSender
{
public function onPaymentProcessed(PaymentProcessedEvent $event): void
{
$payment = $event->getPayment();
// 결제 완료 이메일 발송
$this->sendConfirmationEmail($payment->getUserEmail(), $payment->getAmount());
}
private function sendConfirmationEmail(string $email, float $amount): void
{
// 이메일 발송 로직 구현
mail($email, '결제 확인', "결제 금액: {$amount}원");
}
}
// 리스너 등록
$eventManager->addEventListener(
'payment.processed',
new EmailSender()
);
최적의 관행과 일반적인 문제점
다른 엔티티 수정 피하기
이벤트 리스너는 현재 엔티티의 작업에만 집중해야 합니다. 리스너에서 다른 엔티티를 수정하거나 복잡한 쿼리를 실행하면 성능 문제와 디버깅이 어려운 부작용을 유발할 수 있습니다.
이벤트 전파 순서 주의
여러 리스너가 동일한 이벤트를 감시할 때, 우선순위를 설정하여 실행 순서를 제어할 수 있습니다:
$eventManager->addEventListener(
['prePersist'],
new LoggingListener(),
10 // 높은 우선순위, 먼저 실행
);
$eventManager->addEventListener(
['prePersist'],
new ValidationListener(),
5 // 낮은 우선순위, 나중에 실행
);
이벤트에서의 예외 처리
이벤트 리스너에서 발생한 예외는 현재 작업을 중단시키므로, 적절하게 예외를 처리하고 의미 있는 오류 메시지를 제공해야 합니다:
public function prePersist(LifecycleEventArgs $args): void
{
try {
// 비즈니스 로직
} catch (Exception $e) {
throw new PersistenceException("prePersist 이벤트 처리 실패: " . $e->getMessage(), 0, $e);
}
}