WebアプリケーションでSQLクエリを手動で記述していた時代から長い道のりを歩んできました。 LaravelのEloquent ORMなどのツールを使用すると、データベースをより高いレベルで操作でき、クエリ構文やセキュリティなどのより低いレベルの詳細から解放されます。
Eloquentでの作業を開始すると、必然的にwhere
やjoin
などの演算子が思い付きます。 より高度なものについては、リクエスト(スコープ)、リーダー(アクセサ)、およびミューテータ(ミューテータ)のスコープがあり、クエリを構築する古い方法に対するより表現力豊かな代替手段を提供します。
よく繰り返されるwhere
句とクエリスタブ(スコープ)の代わりとして使用できる他の代替手段を見てみましょう。 このテクノロジーは、別のモデルを継承する新しいEloquentモデルを作成することです。 このようなモデルは、独自のメソッド、リクエストスタブ(スコープ)、リスナー(リスナー)などを追加する機能を保持しながら、親モデルのすべての機能を継承します。 これは通常「単一テーブル継承」と呼ばれますが、「モデル継承」と呼ぶ方が好きです。
例
ほとんどのWebアプリケーションには「管理者」という概念があります。 管理者は、昇格された特権とアプリケーションのサービス部分へのアクセス権を持つ通常のユーザーです。 一般ユーザーと管理者を区別するために、次のように記述します。
$admins = User::where('is_admin', true)->get();
アプリケーションwhere
式が頻繁に繰り返される場合、それをリクエストのローカルスコープに置き換えると便利です。 User
モデルにisAdmin
リクエストisAdmin
をisAdmin
より表現力豊かで再利用されたコードを記述できるようになります。
$admins = User::isAdmin()->get(); // : class User extends Model { public function scopeIsAdmin($query) { $query->where('is_admin', true); } }
先に進み、モデルの継承を使用しましょう。 User
モデルから継承してグローバルリクエストスタブを追加すると、以前よりも正確な結果が得られますが、今では完全に新しいオブジェクトが得られます。 このオブジェクト( Admin
)は、独自のメソッド、リクエストスタブ、およびその他の機能を持つことができます。
$admins = Admin::all(); // : class Admin extends User { protected $table = 'users'; public static function boot() { parent::boot(); static::addGlobalScope(function ($query) { $query->where('is_admin', true); }); } }
注:クエリが正常に機能するには、protected $table = 'users'
変数が必要です。 Eloquentはモデルクラス名を使用してテーブル名を決定します。 したがって、Eloquentは、テーブル名が「ユーザー」ではなく「管理者」であると想定します。これにより、Base table or view not found
エラーが発生します。
Admin
モデルができたので、 User
モデルと機能を簡単に共有できます。 例:
通知
新しいAdmin
モデルにより、すべての管理者に通知を送信するなどの簡単な操作が簡単になりました。
Notification::send(Admin::all(), NewSignUp($user));
確認する
User
モデルでの操作が管理者に限定されている場合は常に、 User
が管理者を表していることを確認する必要があります。
// if ($admin = User::find($id)->is_admin !== true) { throw new Exception; } $admin->impersonate($user);
Admin
グローバルリクエストスタブはAdmin
者のみに制限されるため、 Admin
クラスに対してimpersonate
メソッドをすぐに呼び出すことができます。
Admin::findOrFail($id)->impersonate($user);
モデル工場
テスト中に、以下の例のようにモデルファクトリを使用して、管理者権限を持つUser
モデルを作成する必要がある場合があります。
$admin = factory(User::class)->create(['is_admin' => true]); // // $factory->define(User::class, function () { return [ ... 'is_admin' => false, ]; });
ユーザーが管理者であるものをカプセル化することにより、ファクトリに状態を追加することにより、このコードを改善できます。
$admin = factory(User::class)->states('admin')->create(); // $factory->state(User::class, 'admin', function () { return ['is_admin' => true]; });
間違いなく優れていますが、それでもUser
モデルのインスタンスを取得します。 Admin
モデルの新しいファクトリを定義することにより、 Admin
者権限を持つユーザーも取得しますが、ファクトリはAdmin
モデルのインスタンスを返します。
$admin = factory(Admin::class)->create(); // $factory->define(Admin::class, function () { return ['is_admin' => true] + factory(User::class)->raw(); });
関係は機能しません。
Eloquentがテーブル名を定義するように、モデルクラス名は外部キーとステージングテーブルを定義するために使用されます。 したがって、 Admin
モデルからの関係へのアクセスには問題があります。
Admin::first()->posts; // : Unknown column 'posts.admin_id' // : class Admin extends User { // } class User extends Model { public function posts() { return $this->hasMany(Post::class); } }
Eloquentは、 Post
モデルの各インスタンスにuser_id
フィールドではなくadmin_id
フィールドがあると想定しているため、リレーションにアクセスできません。 User
モデルでuser_id
外部キーを渡すことでこれを修正できます。
// : class Admin extends User { // } class User extends Model { public function posts() { return $this->hasMany(Post::class, 'user_id'); } }
同じ問題が多対多に存在します。 Eloquentは、ステージングテーブルの名前が現在のモデルクラスの名前と一致すると仮定します。
Admin::first()->tags; // : Table 'admin_tag' doesn't exist // : class Admin extends User { // } class User extends Model { public function tags() { return $this->belongsToMany(Tag::class); } ...
ピボットテーブルの名前とリモートキーの名前を明示的に指定することで、この問題を解決することもできます。
// : class Admin extends User { // } class User extends Model { public function tags() { return $this->belongsToMany(Tag::class, 'user_tag', 'user_id'); } ...
リモートキーとピボットテーブルを明示的に定義すると、 Admin
モデルはUser
モデルの関係にアクセスできますが、このソリューションは理想とはほど遠いものです。 これらの一見不要な定義が存在しても、コードは改善されません。
ただし、この問題を自動的に解決するHasParentModel HasParentModel
を作成できます。 この特性は、モデルクラス名を親モデルのクラス名に置き換えます。 GitHub特性コード。
単一テーブルの継承により、さらに進んでLaravelの動作を改善できます。 Laravelアプリケーションでのモデルの作成を簡素化するパッケージを作成し、日々リリースする準備ができています。 お知らせをお見逃しなくTwitterでフォローしてください!
この特性を使用する新しいAdmin
モデルを見てみましょう。
use App\Abilities\HasParentModel; class Admin extends User { use HasParentModel; // : protected $table = 'users' public static function boot() { parent::boot(); static::addGlobalScope(function ($query) { $query->where('is_admin', true); }); } }
これで、 User
モデルの関係は、デフォルト値に依存した状態に戻ることができます。
// : class User extends Model { public function posts() { return $this->hasMany(Post::class); } public function tags() { return $this->belongsToMany(Tag::class); } }
HasParentModel HasParentModel
モデルをクリアし、開発者がモデル内で何か特別なことが起こっていることを理解できるようにします。
モデルの継承
Eloquentモデルの一般的な特性を特定し、その継承を使用してそれらをクリーンにしました。 このテクノロジーにより、より良いオブジェクト名を作成し、アプリケーションにカプセル化できます。 Users
とAdmins
だけでなく、すべてのEloquentモデルで継承を使用できることにAdmins
。 可能性は無限です!
作成し、楽しみ、知識を共有してください。 プロジェクトでこのパターンをどのように使用するかを教えてください! (ツイーター@calebporzioおよび@tightenco )
頑張って