Eloquent 는 모델의 라이프 사이클의 여러 지점을 가로 챌 수 있도록 여러 이벤트를 지원합니다.

create() 메소드를 통해 모델이 삽입 되기 전이나(creating) 삽입후(created), 또는 delete() 메소드를 통해 모델이 삭제되기 전(deleting)이나 삭제된 후(deleted) 등 여러 가지 시점에서 이벤트를 제공하므로 모델의 라이프 사이클에 맞는 코드를 실행할 수 있습니다. (전체 이벤트 목록은 라라벨 한글 메뉴얼중 http://laravel.kr/docs/5.2/events 를 참고하면 됩니다.)

 

모델 이벤트 등록

아직까지 모델 이벤트를 어떻게 활용해야 할지 감이 안 오는 독자분들도 많으실 겁니다. 예를 들어 사용자의 아주 민감한 개인 정보(주민 번호, 계좌 번호등)를 입력받아서 데이타베이스에 저장하는 부분이 있다고 가정해 봅시다.

 

insert 나 update 를 수행하는 코드에서 개인 정보 암호화를 수행하는 것도 방법이지만 해당 기능을 수행할때마다 개인정보 암호화를 해야 한 다는 것을 기억해야 하므로 실수의 여지가 있습니다.

 

created 모델 이벤트를 사용하면 모델이 데이타베이스에 입력되기 전에 자동으로 암호화를 하도록 수행할 수 있으므로 실수 여지가 없어지고 애플리케이션 코드가 간단해 집니다.

 

모델 이벤트 등록은 app/Providers/AppServiceProvider.php 에서 할수 있으며 boot() 메소드에 모델명::이벤트명의 메소드를 추가해 주면 됩니다.

즉 User 모델이 데이타베이스에서 갱신되기 전 이벤트인 updating 를 등록하려면 아래와 같이 User::updating 메소드를 추가하면 됩니다.

public function boot()
{
    User::updating (function($user)
    {
        // 갱신전 이벤트 처리기 등록
    });
}
CODE

 

이벤트 사용 예 : 개인 정보 자동으로 암호화 하기

이제 사용자의 계좌 번호(예: 컬럼명: account)를 입력받아서 데이타베이스에 생성하기 전에 암호화하는 기능을 구현한다고 가정해 봅시다. 생성전이므로 creating 이벤트를 등록하고 파라미터로 전달된 User 모델의 account 프로퍼티에 값이 있을 경우 암호화를 하면 됩니다. 

먼저 사용자 테이블에 account 컬럼을 추가하는 migration 을 생성하여야 합니다.

$ php artisan make:migration add_account_column_user_table --table=users
Created Migration: 2015_07_23_093034_add_account_column_user_table
CODE

추가된 migration 클래스의 up() 에는 account 컬럼을 추가하고 down() 메소드에는 삭제하는 코드를 넣어줍니다. 

class AddAccountColumnUserTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::table('users', function (Blueprint $table) {
            $table->string('account', 1000);
        });
    }
    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::table('users', function (Blueprint $table) {
            $table->dropColumn('account');
        });
    }
}
CODE

암호화시에 크기가 늘어나므로 컬럼 사이즈를 넉넉하게 잡아야 합니다. 특히 MySQL 은 strict mode가 아닐 경우(5.6 버전까지 기본 설정) 컬럼보다 큰 데이타가 입력되면 자동으로 뒷 부분을 잘라버리고 삽입하므로 나중에 복호화가 안 되는 문제가 있습니다.

컬럼이 추가되었으면 migration 을 실행하여 수정 내역을 데이타베이스에 반영합니다.

$ php artisan migrate
CODE

 

이제 모델 이벤트를 등록하기 위해 AppServiceProvider의 boot() 메소드에 라라벨이 제공하는 암호화 기능인 Crypt 클래스의 encrypt() 를 사용하여 이벤트 처리를 합니다.

class AppServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
        \App\User::creating(function($user)
        {
           if (!empty($user->account))
           {
               $user->account = \Crypt::encrypt($user->account);
           }
        });
    }

CODE

이제 User 모델의 대량 할당 예외를 막기 위해 $fillable  변수에 account 를 추가해 줘야 합니다.

protected $fillable = ['name', 'email', 'password', 'account'];
CODE

 

마지막으로 Orm 컨트롤러에 테스트를 위해 User 모델을 입력하는 메소드를 추가합니다.

public function getUserInsert()
{
    $user = \App\User::create([
        'name'=>'Test', 
        'account' => '1234567890', 
        'email' => 'abc@gmail.com', 
        ]);

    return response()->json($user, 
         200, [], JSON_PRETTY_PRINT);
}
CODE

이제 웹 브라우저에서 http://homestead.app/orm/user-insert 에 연결하여 새로운 USer 모델을 생성하면 모델 이벤트에 의해 자동으로 계좌 번호가 암호화되는 것을 확인할 수 있습니다.

암호화시에 개인키는 .env 에 설정된 APP_KEY 를 사용하므로 이 값을 잃어버리면 데이타 복호화를 못하게 되므로 주의 깊게 관리해야 하며 APP_KEY 를 생성하는 php artisan key:gen 명령어는 운영 환경에서는 함부로 재실행하면 안 됩니다.

암호화된 데이타는 Crypt 파사드의 decrypt() 메소드를 사용하여 복호화할 수 있습니다. php artisan tinker 로 REPL을 구동한 후에 위에서 암호화한 값을 복호화해 보겠습니다.

>>> Crypt::decrypt(App\User::find(10)->account)
=> "1234567890"
CODE

 

정상적으로 암호화된 데이타는 위와 같이 원본 데이타가 출력되며 암호화에 사용한 Key 가 다르거나 암호화 데이타가 잘못된 경우 DecryptException 예외가 던져집니다.

 

키가 일치해도 DecryptException with message 'The payload is invalid.'과 같은 예외가 발생하는 경우가 있습니다.

주요 원인중에 하나는 라라벨은 암호화한 데이타를 BASE64로 변환한 후에 키와 IV(Initial Vector)를 포함하는 JSON 데이타로 저장하므로 원본 데이타가 매우 커지게 됩니다.

이 암호화된 데이타를 insert 할 때 MySQL 5.6 이하 버전은 컬럼 길이보다 큰 데이타가 들어올 경우 자동으로 뒷 부분을 잘라서 입력하므로 복호화가 불가능하게 되므로 컬럼 길이를 아주 넉넉하게 잡아야 합니다.

MySQL 의 데이타를 자르는 문제는 http://lesstif.com/x/3gF1AQ 를 참고하세요.