22강 - 이벤트

라라벨 이벤트 시스템은 Observer 또는 PubSub 패턴을 구현할 수 있게 해 준다. 필자가 알고 있는 이벤트 방식 구조체의 잇점 몇가지는 아래와 같다.

  • 사용자에게 빠른 응답을 제공할 수 있다. (Non blocking I/O)
  • 리스너를 여러개 구현하면, 이벤트 하나로 여러가지 작업을 동시에 수행할 수 있다.

시나리오

어떤 목적인지 모르겠지만, 사용자가 로그인 하면 users 테이블에 last_login 필드에 로그인 시간을 업데이트한다고 가정하자.

참고 이벤트는 주로 IO 가 수반되는 경우에 많이 사용된다. smtp 로 이메일을 보낸다든가, HTTP 클라이언트로 외부 서비스로 부터 데이터를 가져온다든가, 파일 시스템으로부터 큰 파일을 읽을 때 등이 대표적인 예이다.

마이그레이션

users 테이블에 last_login 필드를 추가하자.

$ php artisan make:migration add_last_login_to_users_table

마이그레이션 코드를 작성하자.

class AddLastLoginToUsersTable extends Migration
{
    public function up()
    {
        Schema::table('users', function(Blueprint $table) {
            $table->timestamp('last_login')->nullable();
        });
    }

    public function down()
    {
        Schema::table('users', function(Blueprint $table) {
            $table->dropColumn('last_login');
        });
    }
}

마이그레이션을 실행하자.

$ php artisan migrate

User 모델을 수정하자. $dates 속성에 'last_login' 필드를 추가함으로써, 'updated_at', 'created_at' 처럼 Carbon\Carbon 이 제공하는 다양한 메소드에 접근할 수 있다.

class User extends Model implements ...
{
    protected $dates = ['last_login'];
}

구조체를 만들어 보자.

이번에도 app/Http/routes.php 를 사용할 것이다. 제 "16강 사용자 인증 기본기"에서 사용했던 내용을 좀 훔쳐오자.

Route::get('auth', function () {
    $credentials = [
        'email'    => 'john@example.com',
        'password' => 'password'
    ];

    if (! Auth::attempt($credentials)) {
        return 'Incorrect username and password combination';
    }

    Event::fire('user.login', [Auth::user()]);

    var_dump('Event fired and continue to next line...');

    return;
});

Event::listen('user.login', function($user) {
    var_dump('"user.log" event catched and passed data is:');
    var_dump($user->toArray());
});

tinker 로 로그인에 사용할 사용자를 만들자.

$ php artisan tinker
>>> App\User::create([
... 'email' => 'john@example.com',
... 'name' => 'John Doe',
... 'password' => bcrypt('password')
... ]);
=> App\User {#684
     email: "john@example.com",
     name: "John Doe",
     updated_at: "2015-11-10 14:17:48",
     created_at: "2015-11-10 14:17:48",
     id: 11,
   }

서버를 부트업하고 브라우저에서 'auth' Route 로 접근해 보자. Event::fire(string|object $event, mixed $payload) 에서 이벤트를 던지고, Event::listen(string|array $events, mixed $listener)이 이벤트를 받아서 처리하는 식의 구조체이다. Event::fire() 대신 event() Helper Function을 이용할 수도 있다.

이벤트를 처리하자.

구조체가 만들어 졌으니, 시나리오 대로 로그인 시각을 저장하자.

Event::listen('user.login', function($user) {
    $user->last_login = (new DateTime)->format('Y-m-d H:i:s');

    return $user->save();
});

artisan CLI 로도 확인해 보자. 'last_login' 필드가 Carbon\Carbon 인스턴스로 잘 동작하는 것을 확인할 수 있다.

$ php artisan tinker
>>> $user = App\User::find(11);
=> App\User {#672
     id: 11,
     name: "John Doe",
     email: "john@example.com",
     created_at: "2015-11-10 14:17:48",
     updated_at: "2015-11-10 14:24:36",
     last_login: "2015-11-10 14:24:36",
   }
>>> $user->last_login;
=> Carbon\Carbon {#679
     +"date": "2015-11-10 14:24:36.000000",
     +"timezone_type": 3,
     +"timezone": "UTC",
   }

라라벨 공식 문서에서의 이벤트

처음 접하는 분들이 보기에 라라벨 공식 문서의 이벤트 설명은 정말 어렵다. 하지만, 기본적으로 위 예제와 같은 동작을 좀 더 관리하기 편리하도록 쪼개 놓은 것이라고 볼 수 있다.

공식 문서의 event(new PodcastWasPurchased($podcast)) 에서 이벤트 이름과 이벤트 데이터를 객체로 넘긴 것이며, app/Proviers/EventServiceProvider.php 에서 이벤트 이름과 이벤트 핸들러를 연결시켜 준 것이다. 연결된 이벤트 핸들러는 결국은 우리 예제의 Event::listen() 에 두번 째 인자로 전달한 Closure를 별도 클래스로 떼 놓은 것이다.




comments powered by Disqus
목록 토글
keyboard_arrow_up