[Laravel] Eloquentモデルのイベントをフックする4つの方法

Laravelでデータベースの追加や変更を行う際に、オリジナルの処理を行いたい場合についてサンプルを踏まえて説明します。

Eloquentモデルでは様々な処理を行う際に、各種のイベントを発火しようとするので、このイベントをフックすることによってオリジナルの処理を追加することができます。

Laravel公式ページには2つの方法が解説されていますが、これに加えて2つの計4つの方法を解説します。

イベントの種類

イベントを使用すると、特定のモデルクラスがデータベースに保存または更新されるたびに、簡単にオリジナルの処理を実行できます。

目的に沿ったイベントをフックしましょう。

  • retrieved
  • creating
  • created
  • updating
  • updated
  • saving
  • saved
  • deleting
  • deleted
  • restoring
  • restored

1. dispachesEcentプロパティを使用したイベントのフック

Eloquentモデルの$dispatchesEventsプロパティに必要なイベントと処理するクラスを定義することで各イベントをフックできます。

<?php

namespace App;

use App\Events\UserSaved;
use App\Events\UserDeleted;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;

class User extends Authenticatable
{
    use Notifiable;

    /**
     * The event map for the model.
     *
     * @var array
     */
    protected $dispatchesEvents = [
        'saved' => UserSaved::class,
        'deleted' => UserDeleted::class,
    ];
}

イベントクラスの引数に対象のモデルクラス(上記サンプルの場合App\Userクラス)が渡されるので、リスナー等で処理を行う事ができます。

Laravelのイベントクラスについてはこちら

2. オブザーバーを使用したイベントのフック

モデルのイベント処理をまとめて記述したい場合に便利です。

また、Observersはサービスプロバイダでの登録が可能なので、環境の違いによる振る舞いの差を簡単に吸収することが可能です。

<?php

namespace App\Observers;

use App\User;

class UserObserver
{
    /**
     * Handle to the User "created" event.
     *
     * @param  \App\User  $user
     * @return void
     */
    public function created(User $user)
    {
        //
    }

    /**
     * Handle the User "updated" event.
     *
     * @param  \App\User  $user
     * @return void
     */
    public function updated(User $user)
    {
        //
    }

    /**
     * Handle the User "deleted" event.
     *
     * @param  \App\User  $user
     * @return void
     */
    public function deleted(User $user)
    {
        //
    }
}

各イベントに対応したメソッドが呼ばれます。

すべてのメソッドの引数には対象のモデルクラスが渡されます。

作成したオブザーバーをサービスプロバイダへ登録することで、全てのイベントがフックできます。

<?php

namespace App\Providers;

use App\User;
use App\Observers\UserObserver;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
        User::observe(UserObserver::class);
    }

    /**
     * Register the service provider.
     *
     * @return void
     */
    public function register()
    {
        //
    }
}

3. bootメソッドをoverwriteしたイベントのフック

Eloquentモデルでは、自身の初期化時にboot()メソッドが呼ばれます。

このメソッド内で各イベントが発火された際の処理を記述します。

<?php

namespace App;

use App\Notifications\UserRegistration;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;

class User extends Authenticatable
{
    use Notifiable;

    /**
     * The "booting" method of the model.
     *
     * @return void
     */
    protected static function boot()
    {
        parent::boot();

        self::created(function($user){
            $user->notify(new UserRegistration($user));
        });
    }
}

以前は現在公式で解説されている2つの方法はありませんでしたので、この方法でイベントのフックを行っていました。

今回紹介する4つの中では一番シンプルですが、クラスの継承を多様する場合には注意が必要です。

4. Traitを使用したイベントのフック

個人的にはこの方法をよく使っています。

3.で説明したboot()メソッドですが、内部では以下の様な処理が行われています。

    /**
     * The "booting" method of the model.
     *
     * @return void
     */
    protected static function boot()
    {
        static::bootTraits();
    }

    /**
     * Boot all of the bootable traits on the model.
     *
     * @return void
     */
    protected static function bootTraits()
    {
        $class = static::class;

        foreach (class_uses_recursive($class) as $trait) {
            if (method_exists($class, $method = 'boot'.class_basename($trait))) {
                forward_static_call([$class, $method]);
            }
        }
    }

bootTraits()メソッドに注目すると、自身に使われているTraitの一覧を取得する処理が伺えます。

簡単に説明すると、使用しているTraitの中に、「boot + Traitのクラス名」というメソッドが存在する場合に実行されます。

例えば以下の様なTraitを作成して、Eloquentモデルでuseすることで削除時にログを出力することができます。

<?php

namespace App\Traits;

class DeletedLogger extends Authenticatable
{
    /**
     *  Setup model event hooks
     */
    protected static function bootDeletedLogger()
    {
        static::deleted(function ($model) {
            logger('Deleted Model:', ['class' => get_class(model), 'id' => $model->id]);
        });
    }
}
<?php

namespace App;

use App\Traits\DeletedLogger;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;

class User extends Authenticatable
{
    use Notifiable, DeletedLogger;
}
© Xzxzyzyz