메인 콘텐츠로 건너뛰기

이벤트란

Laravel의 이벤트 시스템은 단순한 옵서버 패턴의 구현입니다. 애플리케이션 내에서 일어난 사건(이벤트)을 발화하고, 그에 반응하는 리스너를 정의함으로써 컴포넌트 간의 의존을 최소한으로 억제할 수 있습니다. 예를 들어 “주문이 확정되었다”라는 이벤트를 발화하면, “확인 메일을 보낸다”, “재고를 줄인다”, “Slack에 알린다”라는 여러 리스너가 각각 독립적으로 움직입니다. 주문 처리의 코드는 메일 전송이나 Slack 알림의 구현을 일체 알 필요가 없습니다.
이벤트 클래스는 app/Events 디렉터리에, 리스너 클래스는 app/Listeners 디렉터리에 놓습니다. 어느 쪽도 존재하지 않는 경우 Artisan 명령어가 자동으로 작성합니다.

이벤트와 리스너의 생성

make:eventmake:listener Artisan 명령어로 클래스의 골격을 생성합니다.
php artisan make:event UserRegistered

php artisan make:listener SendWelcomeEmail --event=UserRegistered
인수 없이 실행하면 인터랙티브하게 입력을 요구받습니다.
php artisan make:event

php artisan make:listener

이벤트 등록

이벤트 디스커버리(자동 검출)

기본적으로 Laravel은 app/Listeners 디렉터리를 스캔해 리스너를 자동 등록합니다. handle 또는 __invoke라는 이름의 메서드의 인수 타입으로부터 이벤트와의 매핑을 추론합니다.
use App\Events\UserRegistered;

class SendWelcomeEmail
{
    public function handle(UserRegistered $event): void
    {
        // 웰컴 메일을 전송
    }
}
PHP의 유니온 타입을 사용하면 여러 이벤트를 하나의 메서드로 받을 수 있습니다.
public function handle(UserRegistered|UserUpdated $event): void
{
    // ...
}
리스너를 다른 디렉터리에 놓는 경우 bootstrap/app.php에서 추가 스캔 대상을 지정합니다.
->withEvents(discover: [
    __DIR__.'/../app/Domain/Orders/Listeners',
])
와일드카드를 사용해 여러 디렉터리를 함께 지정할 수 있습니다.
->withEvents(discover: [
    __DIR__.'/../app/Domain/*/Listeners',
])
등록되어 있는 리스너의 목록은 다음 명령어로 확인할 수 있습니다.
php artisan event:list
운영 환경에서는 리스너의 매니페스트를 캐시해 두면 성능이 향상됩니다. 배포 시에 php artisan optimize 또는 php artisan event:cache를 실행해 주십시오. 캐시를 삭제하려면 php artisan event:clear를 사용합니다.

수동 등록

AppServiceProviderboot 메서드에서 Event 파사드를 사용해 수동 등록할 수도 있습니다.
use App\Events\UserRegistered;
use App\Listeners\SendWelcomeEmail;
use Illuminate\Support\Facades\Event;

public function boot(): void
{
    Event::listen(
        UserRegistered::class,
        SendWelcomeEmail::class,
    );
}
클로저로 등록할 수도 있습니다.
use App\Events\UserRegistered;
use Illuminate\Support\Facades\Event;

public function boot(): void
{
    Event::listen(function (UserRegistered $event) {
        // ...
    });
}

이벤트의 정의

이벤트 클래스는 데이터의 그릇입니다. 로직은 갖지 않고, 이벤트에 관련된 정보를 프로퍼티로 유지합니다.
<?php

namespace App\Events;

use App\Models\User;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class UserRegistered
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    public function __construct(
        public User $user,
    ) {}
}
SerializesModels 트레이트에 의해 큐잉된 리스너가 이벤트를 직렬화할 때 Eloquent 모델이 올바르게 취급됩니다.

이벤트 발화

dispatch 스태틱 메서드 또는 event() 헬퍼로 이벤트를 발화합니다.
use App\Events\UserRegistered;

// 스태틱 메서드
UserRegistered::dispatch($user);

// 헬퍼 함수
event(new UserRegistered($user));
조건부로 발화하는 메서드도 준비되어 있습니다.
UserRegistered::dispatchIf($condition, $user);

UserRegistered::dispatchUnless($condition, $user);

데이터베이스 트랜잭션 후에 발화

이벤트를 트랜잭션의 커밋 후에만 발화하고 싶은 경우, ShouldDispatchAfterCommit 인터페이스를 이벤트 클래스에 구현합니다. 트랜잭션이 실패하면 이벤트는 파기됩니다.
<?php

namespace App\Events;

use App\Models\User;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Events\ShouldDispatchAfterCommit;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class UserRegistered implements ShouldDispatchAfterCommit
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    public function __construct(
        public User $user,
    ) {}
}

리스너의 구현

리스너는 handle 메서드로 이벤트를 받습니다. 생성자에서는 서비스 컨테이너가 의존을 자동 주입합니다.
<?php

namespace App\Listeners;

use App\Events\UserRegistered;
use App\Mail\WelcomeMail;
use Illuminate\Support\Facades\Mail;

class SendWelcomeEmail
{
    public function __construct() {}

    public function handle(UserRegistered $event): void
    {
        Mail::to($event->user->email)
            ->send(new WelcomeMail($event->user));
    }
}
리스너의 handle 메서드에서 false를 반환하면 후속 리스너에의 이벤트 전파를 멈출 수 있습니다.

큐잉된 리스너

메일 전송이나 HTTP 요청 등 시간이 걸리는 처리는 큐잉된 리스너로서 비동기로 실행할 수 있습니다. ShouldQueue 인터페이스를 구현하기만 하면 이벤트 발화 시에 리스너가 자동으로 큐에 적재됩니다.
<?php

namespace App\Listeners;

use App\Events\UserRegistered;
use Illuminate\Contracts\Queue\ShouldQueue;

class SendWelcomeEmail implements ShouldQueue
{
    public function handle(UserRegistered $event): void
    {
        // 이 처리는 큐 워커가 비동기로 실행
    }
}
큐잉된 리스너를 사용하기 전에 큐의 설정과 워커의 기동이 필요합니다. 자세한 내용은 큐와 잡 페이지를 참조해 주십시오.

큐의 접속·이름·지연 시간을 커스터마이즈

PHP 어트리뷰트를 사용해 접속처·큐 이름·지연 시간을 설정할 수 있습니다.
<?php

namespace App\Listeners;

use App\Events\UserRegistered;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\Attributes\Connection;
use Illuminate\Queue\Attributes\Delay;
use Illuminate\Queue\Attributes\Queue;

#[Connection('redis')]
#[Queue('emails')]
#[Delay(10)]
class SendWelcomeEmail implements ShouldQueue
{
    public function handle(UserRegistered $event): void
    {
        // ...
    }
}
메서드로 동적으로 값을 정할 수도 있습니다.
public function viaConnection(): string
{
    return 'redis';
}

public function viaQueue(): string
{
    return 'emails';
}

public function withDelay(UserRegistered $event): int
{
    return 10;
}

최대 시도 횟수와 타임아웃

#[Tries]#[Timeout] 어트리뷰트로 실패 시의 거동을 제어할 수 있습니다.
use Illuminate\Queue\Attributes\Tries;
use Illuminate\Queue\Attributes\Timeout;

#[Tries(3)]
#[Timeout(30)]
class SendWelcomeEmail implements ShouldQueue
{
    // ...
}

실패 시의 처리

failed 메서드를 정의하면, 리스너가 최대 시도 횟수를 넘어 실패했을 때의 사후 처리를 기술할 수 있습니다.
use Throwable;

public function failed(UserRegistered $event, Throwable $exception): void
{
    // 관리자에의 통지 등
}

이벤트 서브스크라이버

이벤트 서브스크라이버를 사용하면 관련된 여러 이벤트 핸들러를 하나의 클래스에 정리할 수 있습니다.

서브스크라이버의 작성

subscribe 메서드에서 이벤트와 핸들러의 매핑을 배열로 반환합니다.
<?php

namespace App\Listeners;

use Illuminate\Auth\Events\Login;
use Illuminate\Auth\Events\Logout;
use Illuminate\Events\Dispatcher;

class UserActivitySubscriber
{
    public function handleLogin(Login $event): void
    {
        // 로그인 처리
    }

    public function handleLogout(Logout $event): void
    {
        // 로그아웃 처리
    }

    /**
     * @return array<string, string>
     */
    public function subscribe(Dispatcher $events): array
    {
        return [
            Login::class => 'handleLogin',
            Logout::class => 'handleLogout',
        ];
    }
}

서브스크라이버의 등록

이벤트 디스커버리가 유효한 경우, subscribe 메서드에서 배열을 반환하는 서브스크라이버는 자동 등록됩니다. 수동으로 등록하는 경우, AppServiceProviderboot 메서드에서 Event::subscribe를 호출합니다.
use App\Listeners\UserActivitySubscriber;
use Illuminate\Support\Facades\Event;

public function boot(): void
{
    Event::subscribe(UserActivitySubscriber::class);
}

실전 예시: 사용자 등록 시에 웰컴 메일을 전송

1

이벤트 클래스 작성

php artisan make:event UserRegistered
app/Events/UserRegistered.php를 편집해 등록 사용자를 유지하는 프로퍼티를 추가합니다.
<?php

namespace App\Events;

use App\Models\User;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class UserRegistered
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    public function __construct(
        public User $user,
    ) {}
}
2

리스너 클래스 작성

php artisan make:listener SendWelcomeEmail --event=UserRegistered
메일 전송을 큐로 비동기로 하기 위해 ShouldQueue를 구현합니다.
<?php

namespace App\Listeners;

use App\Events\UserRegistered;
use App\Mail\WelcomeMail;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Support\Facades\Mail;

class SendWelcomeEmail implements ShouldQueue
{
    public function handle(UserRegistered $event): void
    {
        Mail::to($event->user->email)
            ->send(new WelcomeMail($event->user));
    }
}
3

컨트롤러에서 이벤트를 발화

사용자 등록 처리 후에 UserRegistered::dispatch()를 호출합니다.
<?php

namespace App\Http\Controllers\Auth;

use App\Events\UserRegistered;
use App\Models\User;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;

class RegisterController extends Controller
{
    public function store(Request $request): RedirectResponse
    {
        $user = User::create($request->validated());

        UserRegistered::dispatch($user);

        return redirect('/dashboard');
    }
}
RegisterControllerUserRegistered 이벤트를 발화할 뿐이며, 메일 전송의 구현을 알지 못합니다. 향후 “등록 시에 Slack 알림도 보낸다”가 되어도 컨트롤러에는 아무 변경이 불필요합니다.
4

워커 기동

큐잉된 리스너를 처리하기 위해 워커를 기동합니다.
php artisan queue:work
이벤트 디스커버리가 유효한 경우 AppServiceProvider에의 수동 등록은 불필요합니다. app/Listeners 디렉터리에 놓인 리스너는 자동으로 검출됩니다.
php artisan event:list로 등록된 이벤트와 리스너의 목록을 확인할 수 있습니다. 상정 외의 리스너가 등록되어 있지 않은지 정기적으로 체크해 주십시오.
마지막 수정일 2026년 7월 13일