Model - iruma-tea/laravel-base-11 GitHub Wiki

4. Modelについて

4.1. Eloquent ORMapper

ORMapperとは、ObjectとRelationをMappingする仕組みのこと。 LaravelではEloquentというライブラリがORMapperを実現している。

4.2. モデルの作成

 カテゴリモデルを作成するには次のコマンドを実行する
続いて書籍、著者、著者詳細モデルを作成する

  • php artisan make:model Category
  • php artisan make:model Book
  • php artisan make:model Author
  • php artisan make:model AuthorDetail

4.2.1 モデルの操作確認(CLI)

Tinkerを利用してモデルによるレコードのCRUD操作を確認する

  • Tinkerの起動
    • php artisan tinker

4.2.2. マスアサインメント脆弱性

クライアントから更新されてくない値を渡され、更新されてしまう脆弱性。
モデルの更新許可は以下のメンバー変数を定義することで更新を可能とする。

メンバー変数名 説明
protected $fillable 変更を許可する列名を配列で渡す(ホワイトリスト方式)
protected $guarded 変更を許可しない列名を配列で渡す(ブラックリスト方式)

4.3. モデルの操作

メソッド 説明 使用例
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)

4.4. シーディング

Laravelにはデータベースのレコードをまとめて登録する機能があり、シーディングと呼ばれる。 マスターデータやテストデータの登録など、開発時に登録されていてほしいデータの準備に便利

  • シーディングの手順
    1. シーダーファイルを作成する
      1. php artisan make:seeder CategoryTableSeeder
      2. CategoryTableSeederはシーダーファイル名。シーダーファイルはdatabase/seedersフォルダに格納される。
      3. run メソッドにレコードを登録する処理を追加する
      4. 中間テーブルにレコードを追加する場合は「attach」メソッドを利用する
    2. シーダーファイルを実行対象にする
      1. database/sedders/DatabaseSeeder.phpで作成したシーダーファイルをシーディングの対象になるように追記する
      2. $this->call(CategoriesTableSeeder::class);
    3. シーディングを実行する
      1. php artisan db:seed

4.5. ファクトリによるデータ作成

ファクトリ という機能を使うと、テストデータやダミーデータなどを簡単に作成できる。 著者情報(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

4.5.1. Fakerでよく使用されるメソッド(住所)

メソッド 生成データ
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() 電話番号

4.5.2. Fakerでよく使用されるメソッド(ユーザー)

メソッド 生成データ
fake()->name() 名前
fake()->lastName()
fake()->firstName()
fake()->lastKanaName() 姓(カナ)
fake()->firstKanaName() 名(カナ)
fake()->safeEmail() 安全なメールアドレス(exampleドメイン)

4.5.3. Fakerでよく使用されるメソッド(日付)

メソッド 生成データ
fake()->year() 年(1970年以降)
fake()->month()
fake()->dayOfMonth()
fake()->time() 時:分:秒
fake()->date() 年・月・日
fake()->dateTime() 年・月・日 時:分:秒

4.5.4. Fakerでよく使用されるメソッド(文章)

メソッド 生成データ
fake()->word() 単語
fake()->realText() 10~200文字の文章
fake()->realText(50) 10~50文字の文章
fake()->paragraph() 複数の文章
fake()->paragraph(3, true) 3文の間に改行を挟む文章

4.5.5. Fakerでよく使用されるメソッド(数値)

メソッド 生成データ
fake()->randomDigit() 0~9のランダムな変数
fake()->numberBetween(0, 9) 0~9のランダムな変数
fake()->randomFloat(1, 0, 9) 少数1桁の0~9のランダムな実数

4.6. リレーション

リレーショナルデータベースでは、テーブル同士は以下のリレーション(関係性)を持っている。

4.6.1. 一対多(One to Many)

One to Manyの関係は、以下となる。

  • Aテーブルのレコード1件に対して、Bテーブルのレコードが複数件紐づく。
  • Bテーブルのレコード1件に対して、Aテーブルのレコードが1件のみ紐づく。

One to Many

4.6.2. 多対多(Many to Many)

Many to Manyの関係は、以下となる。

  • Aテーブルのレコード1件に対してBテーブルのレコードが複数件紐づく。
  • Bテーブルのレコード1件に対してAテーブルのレコードが複数件紐づく。

Many to Many

4.6.3. 一対一(One to One)

One to Oneの関係は、以下となる。

  • Aテーブルのレコード1件に対してBテーブルのレコードが1件のみ紐づく。
  • Bテーブルのレコード1件に対してAテーブルのレコードが1件のみ紐づく。

One to One

4.7 N+1問題

ORMapperを利用している際に発生するN+1問題があります。N+1問題は、不必要に何度もSQLを発行してしまう問題です。

4.7.1 モデルの裏側で発行したSQLのログの取得

  • DB::enableQueryLog(); を実行することで、モデルの裏側で発行したSQLのログを取得することができる。
  • DB::getQueryLog(); 「DB::enableQueryLog()」実行後に当コマンドでSQLログを表示することができる。

4.7.2 Eager Loading

N+1問題を解決するにはEager Loadingを使用する。Eager Loadingとは、親情報を取得する時点で、
子情報を取得する方法です。

  • Eager Loadingするには、withメソッドを利用する。
    • Author::with('detail')->get();
    • Book::with(['authors', 'category'])->get();
    • Book::with('authors.detail')->get();

4.8. 複雑なSQL

4.8.1. AND条件

whereメソッドを複数チェーンさせた場合は、AND条件を付与していくことになる

  • titleが'p'から始まる、かつ、titleが'g'で終わるレコードを検索
    • Category::where('title', 'like', 'p%')->where('title', 'like', '%g')->get();

4.8.2. OR条件

OR条件を付与したい場合は、orWhereメソッドを利用する。

  • priceが3600以上、または、priceが3100以下のレコードを検索
    • Book::where('price', '>=', 3600)->orWhere('price', '<=', 3100)->get();

4.8.3. 範囲条件

範囲条件を付与するには、whereBetweenメソッド、whereNotBetweenメソッドを利用する

  • priceが3100以上、かつ、4000以下のレコードの検索
    • Book::whereBetween('price',[3100, 4000])->get();
  • priceが3100より小さい、または、4000より大きいレコードの検索
    • Book::whereNotBetween('price', [3100, 4000])->get();

4.8.4. IN条件

IN条件を付与するには、whereInメソッド、whereNotInメソッドを利用する

  • IDが1、または、3のレコードを検索
    • Book::whereIn('id', [1, 3])->get();
  • IDが1でない、かつ、3でもないレコードを検索
    • Book::whereNotIn('id', [1, 3])->get();

4.8.5. NULL条件

NULL条件を付与するにはwhereNullメソッド、whereNotNullメソッドを利用する

  • emailがNullであるレコードを検索
    • AuthorDetail::whereNull('email')->get();
  • emailがNullでないレコードを検索
    • AuthorDetail::whereNotNull('email')->get();

4.8.6. 存在チェック

レコードの存在チェックを行うさいには、existsメソッド、doesntExistメソッドを使用する

  • priceが4000より大きいレコードが存在するか
    • Book::where('price', '>', 4000)->exists();
  • priceが4000より大きいレコードが存在しないか
    • Book::where('price', '>', 4000)->doesntExist();

4.8.7. 並び替え

取得結果を昇順で取得するには、orderByメソッド、降順で取得するさいには、orderByDescメソッドを使用する

  • priceが3500以上のレコードをpriceの昇順で検索
    • Book::where('price', '>=', 3500)->orderBy('price')->get();
  • priceが3500以上のレコードをpriceの降順で検索
    • Book::where('price', '>=', 3500)->orderByDesc('price')->get();

4.8.8. 列指定

selectメソッドを使用することで、取得する列を絞り込むことができる。

  • タイトル列、価格列のみ指定して取得する
    • Book::select('title', 'price')->where('price', '>', 3000)->get();
  • getメソッドに列名を指定しても取得できる
    • Book::where('price', '>', 3000)->get('title', 'price');

4.8.9. 集約関数

集約関数も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');

4.8.10. RawSQL

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();

4.8.11. クエリビルダー

便利な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();

4.9. トランザクション

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() コミットする

4.10. syncメソッド

syncメソッドは、紐づく情報をすべて削除してから、引数に指定されている値で、
新しく関連付けする。

$book->authors()->sync($request->author_ids);

4.11. detachメソッド

detachメソッドを使用して関連テーブルの削除を実施する。
外部キー制約に反さないように子テーブルから削除する。

4.12. カスケードによる削除

カスケードという機能を利用すると、親レコード(書籍)が削除されたときに、
自動的に子レコードも削除するようにできる。
カスケードの設定はマイグレーションファイルで行う。

$table->foreignId('book_id')->constrained()->cascadeOnDelete();

⚠️ **GitHub.com Fallback** ⚠️