概要
ローカルクエリースコープを使うと、特定モデルに必要なクエリービルダーに名前を付けて、1つのメソッドとして呼び出すことができる。
準備
クエリービルダーで使ったOrder
とCustomer
のモデルとテーブルを使う。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
mysql> SELECT * FROM customers; +----+-----------+ | id | name | +----+-----------+ | 1 | customer1 | | 2 | customer2 | | 3 | customer3 | +----+-----------+ 3 rows in set (0.00 sec) mysql> SELECT * FROM orders; +----+-------------+---------------------+---------------+ | id | customer_id | ordered_at | item | +----+-------------+---------------------+---------------+ | 1 | 1 | 2020-06-05 00:00:00 | screw | | 2 | 2 | 2020-06-05 00:00:00 | rubber sheet | | 3 | 2 | 2020-06-06 00:00:00 | plastic plate | | 4 | 1 | 2020-06-07 00:00:00 | rubber sheet | | 5 | 3 | 2020-06-08 00:00:00 | wire | +----+-------------+---------------------+---------------+ 5 rows in set (0.00 sec) |
それぞれのモデルでリレーションを定義している。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
# Customerモデル class Customer extends Model { public $timestamps = false; public function email() { return $this->hasOne('App\Email'); } public function orders() { return $this->hasMany('App\Order'); } } # Orderモデル class Order extends Model { public $timestamps = false; public function customer() { return $this->belongsTo('App\Customer'); } } |
手順
ローカルスコープのクエリービルダーは、次の手順で定義する。
- 対象のモデルでメソッド定義
'scope'
に続いてキャメルケースでメソッド名を定義- 第1引数にクエリー(
$query
など) - ビルダーに引数を渡したいときは第2引数以降に加える
- メソッド内の戻り値は、第1引数のクエリーに対してビルダーを組んだ結果を渡す
- 定義したメソッドをビルダーの様に呼び出す
- 呼び出す際は
'scope'
を除いたメソッド名で呼び出す
- 呼び出す際は
例1:引数なし
たとえばorders
の最新3データのみ取り出す定形操作があるとする。このとき、Order
モデルで以下の様にメソッドを定義する。
メソッド名は頭に'scope'
を付けてscopeLatest3()
として、クエリーを受け取る引数に$query
を設定。
1 2 3 4 5 6 7 8 9 10 11 12 |
class Order extends Model { public $timestamps = false; public function customer() { return $this->belongsTo('App\Customer'); } public function scopeLatest3($query) { return $query->latest('ordered_at')->limit(3); } } |
tinkerでこれを利用してみる。呼び出す際は’scope’を付けずにlatest3()
とし、$query
に対応する引数は渡す必要はない。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
>>> Order::latest3()->get() => Illuminate\Database\Eloquent\Collection {#4315 all: [ App\Order {#4311 id: 5, customer_id: 3, ordered_at: "2020-06-08 00:00:00", item: "wire", }, App\Order {#4321 id: 4, customer_id: 1, ordered_at: "2020-06-07 00:00:00", item: "rubber sheet", }, App\Order {#4328 id: 3, customer_id: 2, ordered_at: "2020-06-06 00:00:00", item: "plastic plate", }, ], } |
生成されるSQLは以下のとおり。
1 2 |
>>> Order::latest3()->toSql() => "select * from `orders` order by `ordered_at` desc limit 3" |
例2:引数を渡す場合
モデルでのメソッド定義の第2引数以下に、渡したい引数を列挙する。以下の例ではcustomer_id
を渡し、これに合致するorders
のデータを抽出している。
実行時に渡したい引数は、第2引数custome_id
で定義している。
1 2 3 |
public function scopeOfCustomer($query, $customer_id) { return $query->where('customer_id', '=', $customer_id); } |
tinkerで引数にcustoer_id=1
を渡して実行してみる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
>>> Order::ofCustomer(1)->get() => Illuminate\Database\Eloquent\Collection {#4323 all: [ App\Order {#4324 id: 1, customer_id: 1, ordered_at: "2020-06-05 00:00:00", item: "screw", }, App\Order {#4326 id: 4, customer_id: 1, ordered_at: "2020-06-07 00:00:00", item: "rubber sheet", }, ], } |
生成されるSQLとバインドされる値は以下のとおり。
1 2 3 4 5 6 |
>>> Order::ofCustomer(1)->toSql() => "select * from `orders` where `customer_id` = ?" >>> Order::ofCustomer(1)->getBindings() => [ 1, ] |
例3:子モデルが絡む場合
ビルダーに引数を渡して、子モデルの条件で抽出したい場合。whereHas()
を使うが、引数に渡すクロージャ―の変数はローカルスコープのため、use
で引数の変数を受け取って渡す。
1 2 3 4 5 6 7 |
public function scopeOfCustomerName($query, $name) { return $query->whereHas('customer', function($query) use($name) { $query->where('name', '=', $name); } ); } |
tinkerでcustomer_name='customer2'
を渡して確認。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
>>> Order::ofCustomerName('customer2')->get() => Illuminate\Database\Eloquent\Collection {#4317 all: [ App\Order {#4329 id: 2, customer_id: 2, ordered_at: "2020-06-05 00:00:00", item: "rubber sheet", }, App\Order {#4313 id: 3, customer_id: 2, ordered_at: "2020-06-06 00:00:00", item: "plastic plate", }, ], } |
生成されるSQLとバインドされる値は以下のとおり。
1 2 3 4 5 6 |
>>> Order::ofCustomerName('customer2')->toSql() => "select * from `orders` where exists (select * from `customers` where `orders`.`customer_id` = `customers`.`id` and `name` = ?)" >>> Order::ofCustomerName('customer2')->getBindings() => [ "customer2", ] |