先週に書いた記事で、Eloquent ORM(以下Eloquent)をライブラリとして導入する方法を書きました。
この記事の中ではじめ、次のようなコードを書いてbooksテーブルについてBookクラスを作り全件を取得して表示しました。
<?php // ダメなコードです class Book extends Model { protected $table = 'books'; protected $fillable = [ 'id', 'isbn', 'name', 'author', 'created', 'updated', ]; } === $books = Book::all()->toArray(); foreach($books as $book) { echo $book['name']; }
このコードは動いてはいましたが、2点の指摘をいただきました。
- toArrayするのはやめましょう
- table、fillableフィールドの指定はいりません。
非常にはずかしいことに、普段Laravelを書いて、Eloquentを使っているというのに知らないことがありすぎたので、本エントリではこれらの指摘とその他に調べた基本的だと思われることをまとめておきます。
以下で記載しているコードの省略なしは、
php-sandbox/basic-eloquent-usage at master · zuckeyM-17/php-sandbox · GitHub
にありますので、確認に使ってみてください。
指摘について
1. toArrayするのはやめましょう
eloquentのcollectionを利用するように変更 · zuckeyM-17/php-sandbox@029ad50 · GitHub
全件取得 ->表示するのは、以下のようにして書けます。
<?php $books = Book::all(); foreach($books as $book) { echo $book->name; }
Eloquentは、all
、get
のメソッドを利用するとCollection
クラスという便利なリストのインスタンスを戻してくれます。
例のようにforeachで回すと、単一のModelが取得できますし、Modelであれば、->
でフィールドにアクセスできます。
toArray()を使うとそれらの恩恵に預かることができないので、ORMを使う旨味が激減します。
また、取得には、他にfind
やfirst
、last
などのメソッドがあります。
それぞれプライマリキーで検索やlimitを書けているのですが、これらは単一のModelを取得することに利用します。
※ findの引数が配列になっている場合は、Collectionが戻ります。
2. table、fillableフィールドの指定はいりません。
不要なfillableとtableの記述を削除 · zuckeyM-17/php-sandbox@1e42688 · GitHub
tableフィールド
EloquentはActiveRecordパターンの実装*1 なので、1つのModelインスタンスは1テーブルの1レコードと完全に一致します。
そのため、テーブルの名前が複数形のスネークケースという慣例に則っていれば、Eloquentはbooks
をBook
モデルに紐付けるといったように、テーブルの指定を推測してくれます。
この他にも、connectionというフィールドも良く明示的に書いていましたが、DBが複数あるなどの特殊な場合を覗いてはDefaultの設定を見るため、指定する必要はありません。
fillableフィールド
fillableフィールドは悪意のあるユーザーがアプリが意図しない変更をDBに与えないように変更可能かどうかをカラム名のホワイトリストで縛るものです。
以下のようにfillableフィールドに指定した名前のカラムには、fill
値を指定してレコードを作成することが可能です。
もし、指定されていたとしても、その値は無視されます。
<?php class Book extends Model { protected $fillable = [ 'isbn', 'name', 'author', ]; } === $newBook = new Book(); $newBook->fill([ 'id' => 111, // 無視される 'isbn' => '978-4-04-429201-0', 'name' => '涼宮ハルヒの憂鬱', 'author' => '谷川 流', ]); $newBook->save();
同様の役割にguardedというフィールドが存在します。
こちらは、fillableとは逆でブラックリストです。変更されたくない値を明示的に指定することができます。
<?php protected $guarded = [ 'created', // createdをfillすることができない ];
その他の基本事項
ここから先は、指摘を受けてからその他に調べて、使ってはいたもののしっかりと理解していなかったり、知らなかったものなどについて紹介します。
ChunkとCursor
公式ドキュメントの以下の項目に記載のあったchunk
メソッドとcursor
メソッドですが、どちらも大量のデータを取得する際にメモリ使用量を抑える際に使うメソッドのようです。
https://laravel.com/docs/5.5/eloquent#chunking-results
ざっくりとした理解だと、
- chunk => 複数回に分けてqueryを発行することにより、メモリ使用量を少なくする
- cursor => カーソルによって1クエリで1行ずつデータを舐めることによりメモリ使用量を少なくする
こちらについては、僕自信の理解が曖昧なため、もう少し調べる必要があると思います。
詳しく理解されている方がいらしたら、コメントで参考サイトなど教えていただきたいです。
Chunk
<?php Book::chunk(2, function ($books) { // 2冊ずつSQLを回す foreach ($books as $book) { echo $book->name , "\n"; } });
Cursor
<?php foreach (Book::cursor() as $book) { // カーソルで1行ずつ処理をする echo $book->name , "\n"; }
上記どちらも、結果として全件の本のタイトルが表示されます。
Query Scope
クエリスコープはall
やget
メソッドを発行する際に、結果を絞り込むのに便利な機能です。
Global Scope
Global Scopeはその定義をそれぞれのModelのboot
メソッド内にてaddGlobalScope
にて宣言すれば、すべてのクエリにて絞込みが利用できます。
<?php use Illuminate\Database\Eloquent\Scope; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Builder; class BookScope implements Scope { public function apply(Builder $builder, Model $model) { $builder->where('id', '>', 2); } } === class Book extends Model { ~~~ protected static function boot() { parent::boot(); static::addGlobalScope(new BookScope); } ~~~ } === $books = Book::all(); // idが2より大きい`Book`のCollectionを取得
また、addGlobalScope
で設定していても、
<?php $books = Book::withoutGlobalScope(BookScope::class)->get();
とすると、絞込みを回避することもできます。
Anonymous Global Scope
こちらは、Scopeクラスを作らなくても、以下のようにboot
メソッド内で絞込み条件を付け足すこともできます。
<?php class Book extends Model { ~~~ protected static function boot() { parent::boot(); static::addGlobalScope('id', function (Builder $builder) { $builder->where('id', '>', 2); }); } ~~~ }
Local Scope
Local Scopeは各Modelに定義します。
scope
というプレフィックスをつけてpublicなメソッドとして宣言し、クエリを引数に取って、決まった絞込みを利用できます。
<?php class Book extends Model { ~~~ public function scopeRecent($query) // scopeはプレフィックス、recent()というメソッドがqueryBuilderで使えるようになる { return $query->where('id', '>', 3); } ~~~ } === $books = Book::recent()->get(); // idが3より大きい`Book`のCollectionを取得
Dynamic Scope
Dynamic ScopeについてもLocal Scope同様各Modelに定義します。 Local Scopeの定義に引数を増やして、絞り込む値を利用時に入れることができます。
まとめ
- 前回のブログについて指摘していただいた実装を見直し、調査しました
- ブログを書くことで改善できてよかったです!
- その他にもEloquentの知らなかった基本的な機能がたくさんあったので、まとめて、自分で書いてみました
- まだ基本の「き」なのでこれからも少しずつ調べていこうと思います
次回はCollectionの様々な使い方、Eventあたりをやりたいです。