Skip to content

이벤트

소개

라라벨의 이벤트는 애플리케이션 내에서 발생하는 다양한 이벤트를 구독하고 수신할 수 있는 간단한 옵저버 패턴 구현을 제공합니다. 이벤트 클래스는 일반적으로 app/Events 디렉토리에 저장되고, 리스너는 app/Listeners에 저장됩니다. Artisan 콘솔 명령을 사용하여 이벤트 및 리스너를 생성할 때 이러한 디렉토리가 생성되므로 애플리케이션에서 이러한 디렉토리가 보이지 않더라도 걱정하지 마세요.

이벤트는 단일 이벤트에 서로 종속되지 않는 여러 리스너가 있을 수 있으므로 애플리케이션의 다양한 측면을 분리하는 좋은 방법입니다. 예를 들어 주문이 배송될 때마다 사용자에게 Slack 알림을 보내고 싶을 수 있습니다. 주문 처리 코드를 Slack 알림 코드에 결합하는 대신 리스너가 수신하여 Slack 알림을 보내는 데 사용할 수 있는 App\Events\OrderShipped 이벤트를 발생시킬 수 있습니다.

이벤트 및 리스너 생성

이벤트와 리스너를 빠르게 생성하려면 make:eventmake:listener Artisan 명령을 사용할 수 있습니다.

php artisan make:event PodcastProcessed
 
php artisan make:listener SendPodcastNotification --event=PodcastProcessed

편의를 위해 추가 인자 없이 make:eventmake:listener Artisan 명령을 호출할 수도 있습니다. 이렇게 하면 Laravel은 클래스 이름을 자동으로 묻고, 리스너를 만들 때 리스너가 수신해야 하는 이벤트를 묻습니다.

php artisan make:event
 
php artisan make:listener

이벤트 및 리스너 등록

이벤트 검색

기본적으로 Laravel은 애플리케이션의 Listeners 디렉토리를 스캔하여 이벤트 리스너를 자동으로 찾아서 등록합니다. Laravel이 handle 또는 __invoke로 시작하는 리스너 클래스 메서드를 찾으면 Laravel은 해당 메서드를 메서드 시그니처에 타입 힌트된 이벤트에 대한 이벤트 리스너로 등록합니다.

use App\Events\PodcastProcessed;
 
class SendPodcastNotification
{
/**
* 주어진 이벤트를 처리합니다.
*/
public function handle(PodcastProcessed $event): void
{
// ...
}
}

PHP의 union 타입을 사용하여 여러 이벤트를 수신할 수 있습니다.

/**
* 주어진 이벤트를 처리합니다.
*/
public function handle(PodcastProcessed|PodcastPublished $event): void
{
// ...
}

리스너를 다른 디렉토리 또는 여러 디렉토리에 저장하려는 경우 애플리케이션의 bootstrap/app.php 파일에서 withEvents 메서드를 사용하여 Laravel에게 해당 디렉토리를 스캔하도록 지시할 수 있습니다.

->withEvents(discover: [
__DIR__.'/../app/Domain/Orders/Listeners',
])

event:list 명령을 사용하여 애플리케이션 내에 등록된 모든 리스너를 나열할 수 있습니다.

php artisan event:list

프로덕션 환경에서의 이벤트 검색

애플리케이션의 속도를 높이려면 optimize 또는 event:cache Artisan 명령어를 사용하여 애플리케이션의 모든 리스너 매니페스트를 캐시해야 합니다. 일반적으로 이 명령어는 애플리케이션의 배포 프로세스의 일부로 실행되어야 합니다. 이 매니페스트는 프레임워크에서 이벤트 등록 프로세스의 속도를 높이는 데 사용됩니다. event:clear 명령어를 사용하여 이벤트 캐시를 삭제할 수 있습니다.

수동으로 이벤트 등록하기

Event 파사드를 사용하여 애플리케이션의 AppServiceProviderboot 메서드 내에서 이벤트와 해당 리스너를 수동으로 등록할 수 있습니다.

use App\Domain\Orders\Events\PodcastProcessed;
use App\Domain\Orders\Listeners\SendPodcastNotification;
use Illuminate\Support\Facades\Event;
 
/**
* 애플리케이션 서비스 부트스트랩.
*/
public function boot(): void
{
Event::listen(
PodcastProcessed::class,
SendPodcastNotification::class,
);
}

event:list 명령어를 사용하여 애플리케이션 내에 등록된 모든 리스너를 나열할 수 있습니다.

php artisan event:list

클로저 리스너

일반적으로 리스너는 클래스로 정의되지만, 애플리케이션의 AppServiceProviderboot 메서드에서 클로저 기반 이벤트 리스너를 수동으로 등록할 수도 있습니다:

use App\Events\PodcastProcessed;
use Illuminate\Support\Facades\Event;
 
/**
* 모든 애플리케이션 서비스를 부트스트랩합니다.
*/
public function boot(): void
{
Event::listen(function (PodcastProcessed $event) {
// ...
});
}

큐에 넣을 수 있는 익명 이벤트 리스너

클로저 기반 이벤트 리스너를 등록할 때, Illuminate\Events\queueable 함수 내에 리스너 클로저를 래핑하여 Laravel이 를 사용하여 리스너를 실행하도록 지시할 수 있습니다:

use App\Events\PodcastProcessed;
use function Illuminate\Events\queueable;
use Illuminate\Support\Facades\Event;
 
/**
* 모든 애플리케이션 서비스를 부트스트랩합니다.
*/
public function boot(): void
{
Event::listen(queueable(function (PodcastProcessed $event) {
// ...
}));
}

큐에 넣을 수 있는 작업과 마찬가지로 onConnection, onQueue, 및 delay 메서드를 사용하여 큐에 넣은 리스너의 실행을 사용자 정의할 수 있습니다:

Event::listen(queueable(function (PodcastProcessed $event) {
// ...
})->onConnection('redis')->onQueue('podcasts')->delay(now()->addSeconds(10)));

익명의 큐에 넣을 수 있는 리스너 실패를 처리하려는 경우, queueable 리스너를 정의하는 동안 catch 메서드에 클로저를 제공할 수 있습니다. 이 클로저는 이벤트 인스턴스와 리스너 실패의 원인이 된 Throwable 인스턴스를 받습니다:

use App\Events\PodcastProcessed;
use function Illuminate\Events\queueable;
use Illuminate\Support\Facades\Event;
use Throwable;
 
Event::listen(queueable(function (PodcastProcessed $event) {
// ...
})->catch(function (PodcastProcessed $event, Throwable $e) {
// 큐에 넣은 리스너가 실패했습니다...
}));

와일드카드 이벤트 리스너

* 문자를 와일드카드 매개변수로 사용하여 리스너를 등록하여 동일한 리스너에서 여러 이벤트를 캡처할 수 있습니다. 와일드카드 리스너는 이벤트 이름을 첫 번째 인수로, 전체 이벤트 데이터 배열을 두 번째 인수로 받습니다:

Event::listen('event.*', function (string $eventName, array $data) {
// ...
});

이벤트 정의

이벤트 클래스는 기본적으로 이벤트와 관련된 정보를 담는 데이터 컨테이너입니다. 예를 들어, App\Events\OrderShipped 이벤트가 Eloquent ORM 객체를 받는다고 가정해 보겠습니다:

<?php
 
namespace App\Events;
 
use App\Models\Order;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
 
class OrderShipped
{
use Dispatchable, InteractsWithSockets, SerializesModels;
 
/**
* 새 이벤트 인스턴스를 생성합니다.
*/
public function __construct(
public Order $order,
) {}
}

보시다시피 이 이벤트 클래스에는 로직이 없습니다. 구매한 App\Models\Order 인스턴스의 컨테이너입니다. 이벤트에서 사용되는 SerializesModels 트레이트는 큐에 넣은 리스너를 사용할 때와 같이 PHP의 serialize 함수를 사용하여 이벤트 객체를 직렬화하면 Eloquent 모델을 정상적으로 직렬화합니다.

리스너 정의

다음으로, 예제 이벤트에 대한 리스너를 살펴보겠습니다. 이벤트 리스너는 handle 메서드에서 이벤트 인스턴스를 받습니다. --event 옵션과 함께 호출된 make:listener Artisan 명령은 자동으로 적절한 이벤트 클래스를 가져오고 handle 메서드에서 이벤트를 타입 힌트합니다. handle 메서드 내에서 이벤트에 대응하는 데 필요한 모든 작업을 수행할 수 있습니다:

<?php
 
namespace App\Listeners;
 
use App\Events\OrderShipped;
 
class SendShipmentNotification
{
/**
* 이벤트 리스너를 생성합니다.
*/
public function __construct() {}
 
/**
* 이벤트를 처리합니다.
*/
public function handle(OrderShipped $event): void
{
// $event->order를 사용하여 주문에 접근합니다...
}
}
lightbulb

이벤트 리스너는 생성자에서 필요한 모든 종속성을 타입 힌트할 수도 있습니다. 모든 이벤트 리스너는 Laravel 서비스 컨테이너를 통해 해결되므로 종속성이 자동으로 주입됩니다.

이벤트 전파 중지

때로는 다른 리스너로의 이벤트 전파를 중지하고 싶을 수 있습니다. 리스너의 handle 메서드에서 false를 반환하여 그렇게 할 수 있습니다.

큐에 넣은 이벤트 리스너

리스너가 이메일 보내기 또는 HTTP 요청과 같은 느린 작업을 수행하는 경우 리스너를 큐에 넣는 것이 유용할 수 있습니다. 큐에 넣은 리스너를 사용하기 전에 큐를 구성하고 서버 또는 로컬 개발 환경에서 큐 워커를 시작해야 합니다.

리스너를 큐에 넣어야 함을 지정하려면 리스너 클래스에 ShouldQueue 인터페이스를 추가합니다. make:listener Artisan 명령으로 생성된 리스너는 이미 이 인터페이스를 현재 네임스페이스로 가져왔으므로 즉시 사용할 수 있습니다:

<?php
 
namespace App\Listeners;
 
use App\Events\OrderShipped;
use Illuminate\Contracts\Queue\ShouldQueue;
 
class SendShipmentNotification implements ShouldQueue
{
// ...
}

그게 다입니다! 이제 이 리스너가 처리하는 이벤트가 디스패치되면 리스너는 Laravel의 큐 시스템을 사용하여 이벤트 디스패처에 의해 자동으로 큐에 들어갑니다. 큐에서 리스너를 실행할 때 예외가 발생하지 않으면 큐에 넣은 작업은 처리가 완료된 후 자동으로 삭제됩니다.

큐 연결, 이름 및 지연 사용자 지정

이벤트 리스너의 큐 연결, 큐 이름 또는 큐 지연 시간을 사용자 지정하려면 리스너 클래스에서 $connection, $queue 또는 $delay 속성을 정의할 수 있습니다:

<?php
 
namespace App\Listeners;
 
use App\Events\OrderShipped;
use Illuminate\Contracts\Queue\ShouldQueue;
 
class SendShipmentNotification implements ShouldQueue
{
/**
* 작업을 보낼 연결 이름입니다.
*
* @var string|null
*/
public $connection = 'sqs';
 
/**
* 작업을 보낼 큐의 이름입니다.
*
* @var string|null
*/
public $queue = 'listeners';
 
/**
* 작업을 처리하기 전 시간(초)입니다.
*
* @var int
*/
public $delay = 60;
}

리스너의 큐 연결, 큐 이름 또는 지연을 런타임에 정의하려면 리스너에서 viaConnection, viaQueue 또는 withDelay 메서드를 정의할 수 있습니다:

/**
* 리스너의 큐 연결 이름을 가져옵니다.
*/
public function viaConnection(): string
{
return 'sqs';
}
 
/**
* 리스너의 큐 이름을 가져옵니다.
*/
public function viaQueue(): string
{
return 'listeners';
}
 
/**
* 작업을 처리하기 전 시간(초)을 가져옵니다.
*/
public function withDelay(OrderShipped $event): int
{
return $event->highPriority ? 0 : 60;
}

조건부로 리스너 큐에 넣기

때로는 런타임에만 사용할 수 있는 일부 데이터를 기반으로 리스너를 큐에 넣어야 하는지 여부를 결정해야 할 수 있습니다. 이를 위해 리스너를 큐에 넣어야 하는지 여부를 결정하기 위해 shouldQueue 메서드를 리스너에 추가할 수 있습니다. shouldQueue 메서드가 false를 반환하면 리스너가 큐에 들어가지 않습니다:

<?php
 
namespace App\Listeners;
 
use App\Events\OrderCreated;
use Illuminate\Contracts\Queue\ShouldQueue;
 
class RewardGiftCard implements ShouldQueue
{
/**
* 고객에게 기프트 카드를 보상합니다.
*/
public function handle(OrderCreated $event): void
{
// ...
}
 
/**
* 리스너를 큐에 넣어야 하는지 여부를 결정합니다.
*/
public function shouldQueue(OrderCreated $event): bool
{
return $event->order->subtotal >= 5000;
}
}

큐와 수동으로 상호 작용

리스너의 기본 큐 작업의 deleterelease 메서드에 수동으로 접근해야 하는 경우 Illuminate\Queue\InteractsWithQueue 트레이트를 사용하여 접근할 수 있습니다. 이 트레이트는 생성된 리스너에서 기본적으로 가져오며 이러한 메서드에 대한 액세스를 제공합니다:

<?php
 
namespace App\Listeners;
 
use App\Events\OrderShipped;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
 
class SendShipmentNotification implements ShouldQueue
{
use InteractsWithQueue;
 
/**
* 이벤트를 처리합니다.
*/
public function handle(OrderShipped $event): void
{
if (true) {
$this->release(30);
}
}
}

큐에 넣은 이벤트 리스너 및 데이터베이스 트랜잭션

데이터베이스 트랜잭션 내에서 큐에 넣은 리스너가 디스패치되면 데이터베이스 트랜잭션이 커밋되기 전에 큐에서 처리될 수 있습니다. 이 경우 데이터베이스 트랜잭션 중에 모델 또는 데이터베이스 레코드에 적용한 모든 업데이트가 데이터베이스에 아직 반영되지 않았을 수 있습니다. 또한 트랜잭션 내에서 생성된 모델 또는 데이터베이스 레코드가 데이터베이스에 존재하지 않을 수 있습니다. 리스너가 이러한 모델에 의존하는 경우 큐에 넣은 리스너를 디스패치하는 작업을 처리할 때 예기치 않은 오류가 발생할 수 있습니다.

큐 연결의 after_commit 구성 옵션이 false로 설정된 경우에도 특정 큐에 넣은 리스너를 열려 있는 모든 데이터베이스 트랜잭션이 커밋된 후에 디스패치해야 함을 표시하기 위해 리스너 클래스에서 ShouldQueueAfterCommit 인터페이스를 구현할 수 있습니다:

<?php
 
namespace App\Listeners;
 
use Illuminate\Contracts\Queue\ShouldQueueAfterCommit;
use Illuminate\Queue\InteractsWithQueue;
 
class SendShipmentNotification implements ShouldQueueAfterCommit
{
use InteractsWithQueue;
}
lightbulb

이러한 문제를 해결하는 방법에 대한 자세한 내용은 큐에 넣은 작업 및 데이터베이스 트랜잭션에 대한 설명서를 참조하십시오.

실패한 작업 처리

때로는 큐에 넣은 이벤트 리스너가 실패할 수 있습니다. 큐에 넣은 리스너가 큐 작업자에 의해 정의된 최대 시도 횟수를 초과하면 리스너에서 failed 메서드가 호출됩니다. failed 메서드는 이벤트 인스턴스와 실패를 일으킨 Throwable을 받습니다:

<?php
 
namespace App\Listeners;
 
use App\Events\OrderShipped;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use Throwable;
 
class SendShipmentNotification implements ShouldQueue
{
use InteractsWithQueue;
 
/**
* 이벤트를 처리합니다.
*/
public function handle(OrderShipped $event): void
{
// ...
}
 
/**
* 작업 실패를 처리합니다.
*/
public function failed(OrderShipped $event, Throwable $exception): void
{
// ...
}
}

큐에 넣은 리스너 최대 시도 횟수 지정

큐에 넣은 리스너 중 하나에서 오류가 발생하는 경우 무기한 재시도하지 않으려 할 것입니다. 따라서 Laravel은 리스너를 시도할 수 있는 횟수 또는 기간을 지정하는 다양한 방법을 제공합니다.

리스너 클래스에 $tries 속성을 정의하여 리스너가 실패한 것으로 간주되기 전에 시도할 수 있는 횟수를 지정할 수 있습니다:

<?php
 
namespace App\Listeners;
 
use App\Events\OrderShipped;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
 
class SendShipmentNotification implements ShouldQueue
{
use InteractsWithQueue;
 
/**
* 큐에 넣은 리스너를 시도할 수 있는 횟수입니다.
*
* @var int
*/
public $tries = 5;
}

리스너가 실패하기 전에 시도할 수 있는 횟수를 정의하는 대신 리스너를 더 이상 시도하지 않아야 하는 시간을 정의할 수 있습니다. 이를 통해 리스너는 지정된 시간 프레임 내에서 임의의 횟수로 시도할 수 있습니다. 리스너를 더 이상 시도하지 않아야 하는 시간을 정의하려면 리스너 클래스에 retryUntil 메서드를 추가합니다. 이 메서드는 DateTime 인스턴스를 반환해야 합니다:

use DateTime;
 
/**
* 리스너가 시간 초과되어야 하는 시간을 결정합니다.
*/
public function retryUntil(): DateTime
{
return now()->addMinutes(5);
}

큐에 넣은 리스너 백오프 지정

예외가 발생한 리스너를 재시도하기 전에 Laravel이 몇 초 동안 기다려야 하는지를 구성하려면 리스너 클래스에서 backoff 속성을 정의하여 그렇게 할 수 있습니다:

/**
* 큐에 넣은 리스너를 재시도하기 전에 기다리는 시간(초)입니다.
*
* @var int
*/
public $backoff = 3;

리스너의 백오프 시간을 결정하기 위해 더 복잡한 로직이 필요한 경우 리스너 클래스에 backoff 메서드를 정의할 수 있습니다:

/**
* 큐에 넣은 리스너를 재시도하기 전에 기다리는 시간(초)을 계산합니다.
*/
public function backoff(): int
{
return 3;
}

backoff 메서드에서 백오프 값의 배열을 반환하여 "지수" 백오프를 쉽게 구성할 수 있습니다. 이 예에서는 재시도 지연 시간이 첫 번째 재시도의 경우 1초, 두 번째 재시도의 경우 5초, 세 번째 재시도의 경우 10초, 나머지 시도가 더 있는 경우 모든 후속 재시도의 경우 10초가 됩니다.

/**
* 큐에 넣은 리스너를 재시도하기 전에 기다리는 시간(초)을 계산합니다.
*
* @return array<int, int>
*/
public function backoff(): array
{
return [1, 5, 10];
}

이벤트 디스패치

이벤트를 디스패치하려면 이벤트에서 정적 dispatch 메서드를 호출할 수 있습니다. 이 메서드는 Illuminate\Foundation\Events\Dispatchable 트레이트에 의해 이벤트에서 사용할 수 있게 됩니다. dispatch 메서드에 전달된 모든 인수는 이벤트의 생성자에 전달됩니다:

<?php
 
namespace App\Http\Controllers;
 
use App\Events\OrderShipped;
use App\Http\Controllers\Controller;
use App\Models\Order;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
 
class OrderShipmentController extends Controller
{
/**
* 지정된 주문을 배송합니다.
*/
public function store(Request $request): RedirectResponse
{
$order = Order::findOrFail($request->order_id);
 
// 주문 배송 로직...
 
OrderShipped::dispatch($order);
 
return redirect('/orders');
}
}

조건부로 이벤트를 디스패치하려면 dispatchIfdispatchUnless 메서드를 사용할 수 있습니다:

OrderShipped::dispatchIf($condition, $order);
 
OrderShipped::dispatchUnless($condition, $order);
lightbulb

테스트할 때, 특정 이벤트가 해당 리스너를 실제로 트리거하지 않고도 디스패치되었는지 확인하는 것이 도움이 될 수 있습니다. Laravel의 내장 테스트 도우미는 이를 매우 간단하게 만듭니다.

데이터베이스 트랜잭션 후 이벤트 디스패치

때로는 활성 데이터베이스 트랜잭션이 커밋된 후에만 Laravel에서 이벤트를 디스패치하도록 지시할 수 있습니다. 이렇게 하려면 이벤트 클래스에서 ShouldDispatchAfterCommit 인터페이스를 구현할 수 있습니다.

이 인터페이스는 Laravel에 현재 데이터베이스 트랜잭션이 커밋될 때까지 이벤트를 디스패치하지 않도록 지시합니다. 트랜잭션이 실패하면 이벤트가 폐기됩니다. 이벤트를 디스패치할 때 진행 중인 데이터베이스 트랜잭션이 없으면 이벤트가 즉시 디스패치됩니다:

<?php
 
namespace App\Events;
 
use App\Models\Order;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Events\ShouldDispatchAfterCommit;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
 
class OrderShipped implements ShouldDispatchAfterCommit
{
use Dispatchable, InteractsWithSockets, SerializesModels;
 
/**
* 새 이벤트 인스턴스를 생성합니다.
*/
public function __construct(
public Order $order,
) {}
}

이벤트 구독자

이벤트 구독자 작성

이벤트 구독자는 구독자 클래스 내에서 여러 이벤트를 구독할 수 있는 클래스이므로 단일 클래스 내에서 여러 이벤트 핸들러를 정의할 수 있습니다. 구독자는 이벤트 디스패처 인스턴스가 전달될 subscribe 메서드를 정의해야 합니다. 지정된 디스패처에서 listen 메서드를 호출하여 이벤트 리스너를 등록할 수 있습니다:

<?php
 
namespace App\Listeners;
 
use Illuminate\Auth\Events\Login;
use Illuminate\Auth\Events\Logout;
use Illuminate\Events\Dispatcher;
 
class UserEventSubscriber
{
/**
* 사용자 로그인 이벤트를 처리합니다.
*/
public function handleUserLogin(Login $event): void {}
 
/**
* 사용자 로그아웃 이벤트를 처리합니다.
*/
public function handleUserLogout(Logout $event): void {}
 
/**
* 구독자의 리스너를 등록합니다.
*/
public function subscribe(Dispatcher $events): void
{
$events->listen(
Login::class,
[UserEventSubscriber::class, 'handleUserLogin']
);
 
$events->listen(
Logout::class,
[UserEventSubscriber::class, 'handleUserLogout']
);
}
}

이벤트 리스너 메서드가 구독자 자체 내에 정의된 경우 구독자의 subscribe 메서드에서 이벤트 및 메서드 이름의 배열을 반환하는 것이 더 편리할 수 있습니다. Laravel은 이벤트 리스너를 등록할 때 구독자의 클래스 이름을 자동으로 결정합니다:

<?php
 
namespace App\Listeners;
 
use Illuminate\Auth\Events\Login;
use Illuminate\Auth\Events\Logout;
use Illuminate\Events\Dispatcher;
 
class UserEventSubscriber
{
/**
* 사용자 로그인 이벤트를 처리합니다.
*/
public function handleUserLogin(Login $event): void {}
 
/**
* 사용자 로그아웃 이벤트를 처리합니다.
*/
public function handleUserLogout(Logout $event): void {}
 
/**
* 구독자의 리스너를 등록합니다.
*
* @return array<string, string>
*/
public function subscribe(Dispatcher $events): array
{
return [
Login::class => 'handleUserLogin',
Logout::class => 'handleUserLogout',
];
}
}

이벤트 구독자 등록

구독자를 작성한 후 Laravel은 이벤트 검색 규칙을 따르는 경우 구독자 내에서 핸들러 메서드를 자동으로 등록합니다. 그렇지 않으면 Event facade의 subscribe 메서드를 사용하여 구독자를 수동으로 등록할 수 있습니다. 일반적으로 이는 애플리케이션의 AppServiceProviderboot 메서드 내에서 수행해야 합니다:

<?php
 
namespace App\Providers;
 
use App\Listeners\UserEventSubscriber;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\ServiceProvider;
 
class AppServiceProvider extends ServiceProvider
{
/**
* 모든 애플리케이션 서비스를 부트스트랩합니다.
*/
public function boot(): void
{
Event::subscribe(UserEventSubscriber::class);
}
}

테스트

이벤트를 디스패치하는 코드를 테스트할 때, 리스너 코드를 해당 이벤트를 디스패치하는 코드와 별도로 직접 테스트할 수 있으므로 Laravel에 이벤트 리스너를 실제로 실행하지 않도록 지시할 수 있습니다. 물론, 리스너 자체를 테스트하려면 리스너 인스턴스를 인스턴스화하고 테스트에서 handle 메서드를 직접 호출할 수 있습니다.

Event facade의 fake 메서드를 사용하면 리스너가 실행되는 것을 방지하고 테스트 중인 코드를 실행한 다음 assertDispatched, assertNotDispatchedassertNothingDispatched 메서드를 사용하여 애플리케이션에서 디스패치한 이벤트를 확인할 수 있습니다:

<?php
 
use App\Events\OrderFailedToShip;
use App\Events\OrderShipped;
use Illuminate\Support\Facades\Event;
 
test('주문을 배송할 수 있습니다', function () {
Event::fake();
 
// 주문 배송 수행...
 
// 이벤트가 디스패치되었는지 확인...
Event::assertDispatched(OrderShipped::class);
 
// 이벤트가 두 번 디스패치되었는지 확인...
Event::assertDispatched(OrderShipped::class, 2);
 
// 이벤트가 디스패치되지 않았는지 확인...
Event::assertNotDispatched(OrderFailedToShip::class);
 
// 어떠한 이벤트도 디스패치되지 않았는지 확인...
Event::assertNothingDispatched();
});
<?php
 
namespace Tests\Feature;
 
use App\Events\OrderFailedToShip;
use App\Events\OrderShipped;
use Illuminate\Support\Facades\Event;
use Tests\TestCase;
 
class ExampleTest extends TestCase
{
/**
* 주문 배송 테스트.
*/
public function test_orders_can_be_shipped(): void
{
Event::fake();
 
// 주문 배송 수행...
 
// 이벤트가 디스패치되었는지 확인...
Event::assertDispatched(OrderShipped::class);
 
// 이벤트가 두 번 디스패치되었는지 확인...
Event::assertDispatched(OrderShipped::class, 2);
 
// 이벤트가 디스패치되지 않았는지 확인...
Event::assertNotDispatched(OrderFailedToShip::class);
 
// 어떠한 이벤트도 디스패치되지 않았는지 확인...
Event::assertNothingDispatched();
}
}

assertDispatched 또는 assertNotDispatched 메서드에 클로저를 전달하여 주어진 "진실 테스트"를 통과하는 이벤트가 디스패치되었는지 확인할 수 있습니다. 주어진 진실 테스트를 통과하는 이벤트가 하나 이상 디스패치되었다면 어설션은 성공합니다.

Event::assertDispatched(function (OrderShipped $event) use ($order) {
return $event->order->id === $order->id;
});

특정 이벤트에 이벤트 리스너가 수신하고 있는지 확인하려면 assertListening 메서드를 사용할 수 있습니다.

Event::assertListening(
OrderShipped::class,
SendShipmentNotification::class
);
exclamation

Event::fake()를 호출한 후에는 이벤트 리스너가 실행되지 않습니다. 따라서 모델의 creating 이벤트 중에 UUID를 생성하는 등 이벤트에 의존하는 모델 팩토리를 테스트에 사용하는 경우, 팩토리를 사용한 후에 Event::fake()를 호출해야 합니다.

이벤트 하위 집합 페이킹하기

특정 이벤트 집합에 대해서만 이벤트 리스너를 페이크하고 싶다면, fake 또는 fakeFor 메서드에 해당 이벤트를 전달할 수 있습니다.

test('orders can be processed', function () {
Event::fake([
OrderCreated::class,
]);
 
$order = Order::factory()->create();
 
Event::assertDispatched(OrderCreated::class);
 
// Other events are dispatched as normal...
$order->update([...]);
});
/**
* Test order process.
*/
public function test_orders_can_be_processed(): void
{
Event::fake([
OrderCreated::class,
]);
 
$order = Order::factory()->create();
 
Event::assertDispatched(OrderCreated::class);
 
// Other events are dispatched as normal...
$order->update([...]);
}

except 메서드를 사용하여 지정된 이벤트 집합을 제외한 모든 이벤트를 가짜로 만들 수 있습니다.

Event::fake()->except([
OrderCreated::class,
]);

범위 지정 이벤트 가짜

테스트의 일부에 대해서만 이벤트 리스너를 가짜로 만들고 싶다면 fakeFor 메서드를 사용할 수 있습니다.

<?php
 
use App\Events\OrderCreated;
use App\Models\Order;
use Illuminate\Support\Facades\Event;
 
test('orders can be processed', function () {
$order = Event::fakeFor(function () {
$order = Order::factory()->create();
 
Event::assertDispatched(OrderCreated::class);
 
return $order;
});
 
// 이벤트는 정상적으로 발송되고 옵저버가 실행됩니다...
$order->update([...]);
});
<?php
 
namespace Tests\Feature;
 
use App\Events\OrderCreated;
use App\Models\Order;
use Illuminate\Support\Facades\Event;
use Tests\TestCase;
 
class ExampleTest extends TestCase
{
/**
* Test order process.
*/
public function test_orders_can_be_processed(): void
{
$order = Event::fakeFor(function () {
$order = Order::factory()->create();
 
Event::assertDispatched(OrderCreated::class);
 
return $order;
});
 
// 이벤트는 정상적으로 발송되고 옵저버가 실행됩니다...
$order->update([...]);
}
}