Model - iruma-tea/laravel-base-11 GitHub Wiki
ORMapperとは、ObjectとRelationをMappingする仕組みのこと。 LaravelではEloquentというライブラリがORMapperを実現している。
カテゴリモデルを作成するには次のコマンドを実行する
続いて書籍、著者、著者詳細モデルを作成する
php artisan make:model Category
php artisan make:model Book
php artisan make:model Author
php artisan make:model AuthorDetail
Tinkerを利用してモデルによるレコードのCRUD操作を確認する
- Tinkerの起動
php artisan tinker
クライアントから更新されてくない値を渡され、更新されてしまう脆弱性。
モデルの更新許可は以下のメンバー変数を定義することで更新を可能とする。
メンバー変数名 | 説明 |
---|---|
protected $fillable | 変更を許可する列名を配列で渡す(ホワイトリスト方式) |
protected $guarded | 変更を許可しない列名を配列で渡す(ブラックリスト方式) |
メソッド | 説明 | 使用例 |
---|---|---|
save | レコードを登録する | $category = new Category(); $category->title = 'programming'; $category->save() |
create | レコードを登録する | Category::create(['title' => 'design',]); |
find | 主キーを条件に1件レコードを取得する | Category::find(1); |
findOrFail | データが存在しないとき、エラーを返却する | Category::findOrFail(3); |
all | テーブルのレコードを全件取得する | Category::all(); |
where | 条件付きでレコードを取得する | Category::where('title', '=', 'programming')->get(); |
update | レコードを更新するさいにはupdateメソッドを使用する | $category->title = 'management'; $category->update(); |
delete | レコードを削除する | $category = Category::find(1); $category->delete(); |
destroy | 主キーを渡すことでレコードを削除する | Category::destroy(2) |
Laravelにはデータベースのレコードをまとめて登録する機能があり、シーディングと呼ばれる。 マスターデータやテストデータの登録など、開発時に登録されていてほしいデータの準備に便利
- シーディングの手順
- シーダーファイルを作成する
php artisan make:seeder CategoryTableSeeder
- CategoryTableSeederはシーダーファイル名。シーダーファイルはdatabase/seedersフォルダに格納される。
- run メソッドにレコードを登録する処理を追加する
- 中間テーブルにレコードを追加する場合は「attach」メソッドを利用する
- シーダーファイルを実行対象にする
- database/sedders/DatabaseSeeder.phpで作成したシーダーファイルをシーディングの対象になるように追記する
$this->call(CategoriesTableSeeder::class);
- シーディングを実行する
php artisan db:seed
- シーダーファイルを作成する
ファクトリ という機能を使うと、テストデータやダミーデータなどを簡単に作成できる。 著者情報(Authors)ファクトリを作成する
-
php artisan make:factory AuthorFactory
'name' => fake()->name(), // ランダムに人名を生成
- シーダーファイルの作成
php artisan make:seeder AuthorsTableSeeder
Author::factory(5)->create();
- ファイルを実行対象にする
$this->call(AuthorsTableSeeder::class);
- シーディングを実行する
php artisan db:seed --class=AuthorsTableSeeder
メソッド | 生成データ |
---|---|
fake()->country() | 国 |
fake()->postcode() | 郵便番号 |
fake()->postcode1() | 郵便番号上桁(3桁) |
fake()->postcode2() | 郵便番号下桁(4桁) |
fake()->address() | 住所 |
fake()->prefecture() | 都道府県 |
fake()->ward() | 区 |
fake()->city() | 市 |
fake()->streetName() | 町 |
fake()->areaNumber() | 番地 |
fake()->secondaryAddress() | 建物名など |
fake()->streetAddress() | 町以降 |
fake()->company() | 会社名 |
fake()->phoneNumber() | 電話番号 |
メソッド | 生成データ |
---|---|
fake()->name() | 名前 |
fake()->lastName() | 姓 |
fake()->firstName() | 名 |
fake()->lastKanaName() | 姓(カナ) |
fake()->firstKanaName() | 名(カナ) |
fake()->safeEmail() | 安全なメールアドレス(exampleドメイン) |
メソッド | 生成データ |
---|---|
fake()->year() | 年(1970年以降) |
fake()->month() | 月 |
fake()->dayOfMonth() | 日 |
fake()->time() | 時:分:秒 |
fake()->date() | 年・月・日 |
fake()->dateTime() | 年・月・日 時:分:秒 |
メソッド | 生成データ |
---|---|
fake()->word() | 単語 |
fake()->realText() | 10~200文字の文章 |
fake()->realText(50) | 10~50文字の文章 |
fake()->paragraph() | 複数の文章 |
fake()->paragraph(3, true) | 3文の間に改行を挟む文章 |
メソッド | 生成データ |
---|---|
fake()->randomDigit() | 0~9のランダムな変数 |
fake()->numberBetween(0, 9) | 0~9のランダムな変数 |
fake()->randomFloat(1, 0, 9) | 少数1桁の0~9のランダムな実数 |
リレーショナルデータベースでは、テーブル同士は以下のリレーション(関係性)を持っている。
One to Manyの関係は、以下となる。
- Aテーブルのレコード1件に対して、Bテーブルのレコードが複数件紐づく。
- Bテーブルのレコード1件に対して、Aテーブルのレコードが1件のみ紐づく。
Many to Manyの関係は、以下となる。
- Aテーブルのレコード1件に対してBテーブルのレコードが複数件紐づく。
- Bテーブルのレコード1件に対してAテーブルのレコードが複数件紐づく。
One to Oneの関係は、以下となる。
- Aテーブルのレコード1件に対してBテーブルのレコードが1件のみ紐づく。
- Bテーブルのレコード1件に対してAテーブルのレコードが1件のみ紐づく。
ORMapperを利用している際に発生するN+1問題があります。N+1問題は、不必要に何度もSQLを発行してしまう問題です。
- DB::enableQueryLog(); を実行することで、モデルの裏側で発行したSQLのログを取得することができる。
- DB::getQueryLog(); 「DB::enableQueryLog()」実行後に当コマンドでSQLログを表示することができる。
N+1問題を解決するにはEager Loadingを使用する。Eager Loadingとは、親情報を取得する時点で、
子情報を取得する方法です。
-
Eager Loadingするには、withメソッドを利用する。
Author::with('detail')->get();
Book::with(['authors', 'category'])->get();
Book::with('authors.detail')->get();
whereメソッドを複数チェーンさせた場合は、AND条件を付与していくことになる
- titleが'p'から始まる、かつ、titleが'g'で終わるレコードを検索
Category::where('title', 'like', 'p%')->where('title', 'like', '%g')->get();
OR条件を付与したい場合は、orWhereメソッドを利用する。
- priceが3600以上、または、priceが3100以下のレコードを検索
Book::where('price', '>=', 3600)->orWhere('price', '<=', 3100)->get();
範囲条件を付与するには、whereBetweenメソッド、whereNotBetweenメソッドを利用する
- priceが3100以上、かつ、4000以下のレコードの検索
Book::whereBetween('price',[3100, 4000])->get();
- priceが3100より小さい、または、4000より大きいレコードの検索
Book::whereNotBetween('price', [3100, 4000])->get();
IN条件を付与するには、whereInメソッド、whereNotInメソッドを利用する
- IDが1、または、3のレコードを検索
Book::whereIn('id', [1, 3])->get();
- IDが1でない、かつ、3でもないレコードを検索
Book::whereNotIn('id', [1, 3])->get();
NULL条件を付与するにはwhereNullメソッド、whereNotNullメソッドを利用する
- emailがNullであるレコードを検索
AuthorDetail::whereNull('email')->get();
- emailがNullでないレコードを検索
AuthorDetail::whereNotNull('email')->get();
レコードの存在チェックを行うさいには、existsメソッド、doesntExistメソッドを使用する
- priceが4000より大きいレコードが存在するか
Book::where('price', '>', 4000)->exists();
- priceが4000より大きいレコードが存在しないか
Book::where('price', '>', 4000)->doesntExist();
取得結果を昇順で取得するには、orderByメソッド、降順で取得するさいには、orderByDescメソッドを使用する
- priceが3500以上のレコードをpriceの昇順で検索
Book::where('price', '>=', 3500)->orderBy('price')->get();
- priceが3500以上のレコードをpriceの降順で検索
Book::where('price', '>=', 3500)->orderByDesc('price')->get();
selectメソッドを使用することで、取得する列を絞り込むことができる。
- タイトル列、価格列のみ指定して取得する
Book::select('title', 'price')->where('price', '>', 3000)->get();
- getメソッドに列名を指定しても取得できる
Book::where('price', '>', 3000)->get('title', 'price');
集約関数もBuilderクラスのメソッドとして定義されている
メソッド | 説明 |
---|---|
count | レコードの件数を計算する |
max | 指定した列の最大値を計算する |
min | 指定した列の最小値を計算する |
avg | 指定した列の平均値を計算する |
sum | 指定した列の最大値を計算する |
- 件数を取得する
Book::where('id', '>', 1)->count();
- priceの最大値を計算する
Book::where('id', '>', 1)->max('price');
- priceの最小値を計算する
Book::where('id', '>', 1)->min('price');
- priceの平均値を計算する
Book::where('id', '>', 1)->avg('price');
- price列の合計値を計算する
Book::where('id', '>', 1)->sum('price');
Raw系のメソッドを使用することで、より自由にSQLを生成することができる。
メソッド | 説明 |
---|---|
selectRaw | SELECT句を生のSQLで指定する |
whereRaw、orWhereRaw | WHERE句を生のSQLで指定する |
orderByRaw | ORDER BY句を生のSQLで指定する |
groupByRaw | GROUP BY句を生のSQLで指定する |
havingRaw、orHavingRaw | HAVING句を生のSQLで指定する |
- IDが1または2のレコードをpriceの降順で検索
Book::selectRaw('title, price')->whereRaw('id in(1, 2)')->orderByRaw('price desc')->get();
- カテゴリIDごとにpriceの合計値を計算
Book::selectRaw('category_id, sum(price)')->groupBy('category_id')->get();
- カテゴリIDごとにpriceの合計値を計算後、合計値が12000以上のレコードを検索
Book::selectRaw('category_id, sum(price) as sum')->groupBy('category_id')->having('sum', '>', '12000')->get();
- 内部結合でカテゴリと書籍のタイトルを検索する
Category::select('categories.title as category_title', 'books.title as book_title')->join('books', 'categories.id', '=', 'books.category_id')->get();
- 外部結合でカテゴリと書籍のタイトルを検索する
Category::select('categories.title as category_title', 'books.title as book_title')->leftJoin('books', 'categories.id', '=', 'books.category_id')->get();
- 結合条件が複数になる場合
Category::select('categories.title as category_title', 'books.title as book_title')->leftJoin('books', function($join){$join->on('categories.id', '=', 'books.category_id')->where('books.price', '>', 3000)->where('books.price', '<', 6000);})->get();
便利なEloquentであるが、以下の弱点もある
-
実際に実行されるSQLが見えにくく、SQLのカスタマイズが難しい
-
結果をオブジェクトに詰め込むため、件数が増えると遅くなる
-
結合するモデルとテーブルの関係性が崩れる
-
クエリビルダーを使用した例
use \Illuminate\Support\Facades\DB;
DB::table('authors')->selectRaw('count(books.id), authors.name')->
join('author_book', 'authors.id', '=', 'author_book.author_id')->
join('books', 'author_book.book_id', '=', 'books.id')->
join('categories', 'books.category_id', '=', 'categories.id')->
where('categories.title', '=', 'programming')->groupBy('authors.name')->get();
Laravelでトランザクションを扱うには、DBファサードのtransactionを使う。
- DBファサードのtransactionを使用した例
- ロールバックの例
use \Illuminate\Support\Facades\DB;
use App\Models\Category;
DB::transaction(function() {
Category::create(['title' => 'science']);
throw new Exception();
});
- コミットの例
use \Illuminate\Support\Facades\DB;
use App\Models\Category;
DB::transaction(function() {
Category::create(['title' => 'science']);
});
- ロールバックの例
手動でトランザクション」を操作するメソッド
メソッド | 説明 |
---|---|
DB:beginTransaction() | トランザクションを開始する |
DB:rollback() | ロールバックする |
DB:commit() | コミットする |
syncメソッドは、紐づく情報をすべて削除してから、引数に指定されている値で、
新しく関連付けする。
$book->authors()->sync($request->author_ids);
detachメソッドを使用して関連テーブルの削除を実施する。
外部キー制約に反さないように子テーブルから削除する。
カスケードという機能を利用すると、親レコード(書籍)が削除されたときに、
自動的に子レコードも削除するようにできる。
カスケードの設定はマイグレーションファイルで行う。
$table->foreignId('book_id')->constrained()->cascadeOnDelete();