はじめに
なんか便利なlaravelの機能
前回の記事で話しましたが、laravelには開発に便利な機能がたくさんあります‼
まだlaravelでしか開発したことないので他のフレームワークと比べて〇〇なんてことは全く言えませんが...
便利な機能を知ることでよりスッキリしたコーディングが可能になったので紹介します‼
用語紹介
用語 | 解説 |
---|---|
リレーション | テーブル同士を紐づける設定。 リレーションが行われると1つのデータ取得時に、紐づけられたテーブルのデータも取得される。 |
ソフトデリート | 物理的にはデータを削除せず、データは残したまま無効化し削除されたものとして扱う機能 |
メインテーブル | メインとなる(データを直接問い合わせられる)テーブル (※1) |
サブテーブル | メインテーブルに紐づけられる形で、データを間接的に問い合わせられるテーブル。 (※1) |
中間テーブル | メインテーブルとサブテーブルの紐づけの際に必要があれば登場するテーブル (※1) |
メインモデルファイル | メインテーブルの設定を記述するモデルファイル (※1) |
サブモデルファイル | サブテーブルの設定を記述するモデルファイル (※1) |
中間モデルファイル | 中間テーブルの設定を記述するモデルファイル (※1) |
(※1)いずれも公式名称ではありませんが、説明の都合で便宜上そう呼ぶことにします。
モデルファイルそのものについては前回の記事を参考にしてください!
解説する機能
今回の説明は以下の機能になります。順に説明していきます。
- リレーション
- ソフトデリート(論理削除)
リレーション
概要
リレーションとはテーブル同士の連携を指します。
(SQL文のテーブルJOINの概念を知っていると理解が速いと思います。)
例えば、県の情報を持つprefecturesテーブルと市の情報を持つcitiesテーブルがあるとします。
■prefecturesテーブル
id | prefecture_name |
---|---|
1 | 熊本県 |
2 | 山口県 |
3 | 青森県 |
■citiesテーブル
id | city_name |
---|---|
1 | 下関市 |
2 | 弘前市 |
3 | 八代市 |
ここからprefecturesテーブルにリレーションを示すカラム(この例では city_id)を追加することで2つのテーブルを紐づけます。
⚠テーブル名は複数形(~s)ですが、カラム名は単数形になります。cities_idとはしない!
■prefecturesテーブル
id | prefecture_name | city_id |
---|---|---|
1 | 熊本県 | 3 |
2 | 山口県 | 1 |
3 | 青森県 | 2 |
このリレーション設定があれば、メインとなるprefecturesテーブルを参照したときに
熊本県→八代市、山口県→下関市、青森県→弘前市というcitiesテーブルの情報も芋づる式に取得できるということです。
また、このリレーション設定は「/app/Models」配下の該当テーブルに対応するモデルファイルに記載することになります。
種類
リレーションには大きく4種類あり、用途によって使い分けることが重要です。
種類 | 解説 | 用途の例 |
---|---|---|
hasOne 1:1 |
1つのidに、 1レコードが対応 |
1テーブルだけだとカラム数が 膨大になるなどの都合で 複数テーブルにカラム分割している 場合に、 対応するレコードを取得する際に。 |
hasMany 1:n |
1つのidに、 複数レコードが対応 |
県に紐づく複数の市の情報を 取得する際に。 |
belongsTo n:1 |
複数idに、 1レコードが対応 |
複数の市に紐づく同じ県の情報を 取得する際に。 |
belongsToMany n:n |
中間テーブル経由で、 2テーブル間で 複数レコードが 双方向に対応 |
県ごとに紐づく複数の特産物の情報を 取得する際に。 |
では改めて種類ごとにイメージとモデルファイル/コントローラファイル設定を見ていきましょう。
hasOne(1:1)
-
イメージ
メインテーブルとサブテーブルのレコードが1:1で対応します。
⚠対応データが複数/対応データが無い場合は利用できません。■prefecturesテーブル(リレーション設定込み)
id prefecture_name population_id 1 熊本県 1 2 山口県 2 3 青森県 3 ■populationsテーブル
id population[万人] 1 160 2 130 3 120 ★取得されるデータ
設定により以下のようなリレーションが成立し、指定したidに該当する行のデータが取得できます。id prefecture_name population_id population[万人] 1 熊本県 1 160 2 山口県 2 130 3 青森県 3 120
- メインモデルファイル設定内容
// メインモデルファイルにてサブモデルファイルの利用を宣言 use App\Models\Population; // 任意の名前でfunctionを作成 public function relatePopulations() { // populationsテーブルとの紐づけを設定 return $this->hasOne(Population::class); }
- コントローラファイル設定内容
コントローラからモデルファイルを参照するためにこの設定が必要になります。// コントローラファイルにて、リレーション設定を記載したモデルファイルの利用を宣言 use App\Models\Prefecture; $prefectureId = 3; //青森県 // リレーション設定されたサブテーブルのデータを取得 // 例)青森県のidをkeyに、人口が120万人というデータを取得 $ans = Prefecture::find($prefectureId)->relatePopulations;
hasMany(1:n)
-
イメージ
メインテーブルの1レコードがサブテーブルの複数レコードに紐づきます。■prefecturesテーブル
id prefecture_name 1 熊本県 2 山口県 ■citiesテーブル(リレーション設定込み)
id city_name prefecture_id 1 熊本市 1 2 八代市 1 3 山口市 2 4 下関市 2 ★取得されるデータ
設定により以下のようなリレーションが成立し、指定したidに該当する行のデータが取得できます。id prefecture_name prefecture_id city_name 1 熊本県 1 熊本市 1 熊本県 1 八代市 2 山口県 2 山口市 2 山口県 2 下関市
- メインモデルファイル設定内容
// メインモデルファイルにてサブモデルファイルの利用を宣言 use App\Models\City; // 任意の名前でfunctionを作成 public function relateCities() { // citiesテーブルとの紐づけを設定 return $this->hasMany(City::class); }
- コントローラファイル設定内容
// コントローラファイルにて、リレーション設定を記載したモデルファイルの利用を宣言 use App\Models\Prefecture; $prefectureId = 2; //山口県 // リレーション設定されたサブテーブルのデータを取得 // 例)山口県のidをkeyに、所属している市が山口市,下関市というデータを取得 $ans = Prefecture::find($prefectureId)->relateCities;
belongsTo(n:1)
-
イメージ
メインテーブルの複数レコードがサブテーブルの1レコードに紐づきます。■citiesテーブル(リレーション設定込み)
id city_name prefecture_id 1 熊本市 1 2 八代市 1 3 山口市 2 4 下関市 2 ■prefecturesテーブル
id prefecture_name 1 熊本県 2 山口県 ★取得されるデータ
設定により以下のようなリレーションが成立し、指定したidに該当する行のデータが取得できます。id city_name prefecture_id prefecture_name 1 熊本市 1 熊本県 2 八代市 1 熊本県 3 山口市 2 山口県 4 下関市 2 山口県
- メインモデルファイル設定内容
// メインモデルファイルにてサブモデルファイルの利用を宣言 use App\Models\Prefecture; // 任意の名前でfunctionを作成 public function relatePrefectures() { // prefecturesテーブルとの紐づけを設定 return $this->belongsTo(Prefecture::class); }
- コントローラファイル設定内容
// コントローラファイルにて、リレーション設定を記載したモデルファイルの利用を宣言 use App\Models\Cities; $cityId = 1; //熊本市 // リレーション設定されたサブテーブルのデータを取得 // 例)熊本市のidをkeyに、所属する県が熊本県というデータを取得 $ans = City::find($cityId)->relatePrefectures;
belongsToMany(n:n)
-
イメージ
中間テーブル(prefecture_specialityテーブル)を経由して2つのテーブルを紐づけます。⚠中間テーブルには命名法則があります。
- 2つのテーブル名(単数形)をアルファベット順にアンダーバー「_」で繋げる
- カラムも左からアルファベット順
■prefecturesテーブル
id prefecture_name 1 熊本県 2 山口県 3 青森県 ■prefecture_specialityテーブル
id prefecture_id speciality_id created_at 1 1 1 2025-02-17 01:11:11 2 1 2 2025-02-17 02:11:11 3 2 3 2025-02-17 03:11:11 4 3 3 2025-02-17 04:11:11 ■specialitiesテーブル
id speciality_name 1 馬肉 2 すいか 3 りんご ★取得されるデータ
設定により以下のようなリレーションが成立し、指定したidに該当する行のデータが取得できます。- prefecturesテーブルをメインテーブルとするとき
id prefecture_name prefecture_id speciality_id speciality_name 1 熊本県 1 1 馬肉 1 熊本県 1 2 すいか 2 山口県 2 3 りんご 3 青森県 3 3 りんご
- specialityテーブルをメインテーブルとするとき
id speciality_name speciality_id prefecture_id prefecture_name 1 馬肉 1 1 熊本県 2 すいか 2 1 熊本県 3 りんご 3 2 山口県 3 りんご 3 3 青森県
-
メインモデルファイル設定内容
- prefecturesテーブルをメインテーブルとするとき)
// メインモデルファイルにて、サブモデルファイルの利用を宣言 use App\Models\Speciality; // 任意の名前でfunctionを作成 public function relateMiddles() { // middleテーブルを経由したspecialitiesテーブルとの紐づけを設定 // belongsToMany([目的の紐づけ先]::class, '[中間テーブル名]', '[メインテーブルのid]', '[サブテーブルのid]') return $this->belongsToMany(Speciality::class, 'prefecture_speciality', 'prefecture_id', 'speciality_id') }
- specialityテーブルをメインテーブルとするとき
// メインモデルファイルにて、サブモデルファイルの利用を宣言 use App\Models\Prefecture; // 任意の名前でfunctionを作成 public function relateMiddles() { // middleテーブルを経由したprefecturesテーブルとの紐づけを設定 return $this->belongsToMany(Prefecture::class, 'prefecture_speciality', 'prefecture_id', 'speciality_id') }
- prefecturesテーブルをメインテーブルとするとき)
- コントローラファイル設定内容
// コントローラファイルにて、リレーション設定を記載したモデルファイルの利用を宣言 use App\Models\Prefecture; use App\Models\Speciality; $prefectureId = 1; //熊本県 $specialityId = 3; //りんご // 中間テーブル経由でリレーション設定されたサブテーブルのデータを取得 // 例)熊本県のidをkeyに、特産物が馬肉,すいかというデータを取得 $ans = Prefecture::find($prefectureId)->relateMiddles; // 例)りんごのidをkeyに、それが特産物な山口県,青森県というデータを取得 $ans = Speciality::find($specialityId)->relateMiddles;
-
(補足)中間テーブルへのデータ追加/削除する方法
コントローラファイルに、以下を必要に応じて記述します。$prefectureId = 1; //熊本県 $specialityId1 = 1; //馬肉 $specialityId2 = 2; //すいか // 1レコードだけの追加 // 例)"県:熊本県-特産品:馬肉"という関連を追加 $prefectureId->relateMiddles()->attach($specialityId1); // 複数レコードを追加 // 例)"県:熊本県-特産品:馬肉", "県:熊本県-特産品:すいか"という関連を追加 $prefecture->relateMiddles()->attach([ $specialityId1 => ['created_at' => now()], $specialityId2 => ['created_at' => now()] ]); // 1レコードだけの削除 // 例)"県:熊本県-特産品:馬肉"という関連を削除 $prefectureId->relateMiddles()->detach($specialityId1); // 複数レコードを削除 // 例)"県:熊本県-特産品:馬肉", "県:熊本県-特産品:すいか"という関連を削除 $prefectureId->relateMiddles()->detach([$specialityId1, $specialityId2]); // 全ての関連付けを解除 // 例)"県:熊本県"の関連をすべて削除 $prefectureId->relateMiddles()->detach();
ソフトデリート(論理削除)
概要
データ削除の際に物理的にレコード削除してしまうと、何かあった場合に復元できなくなってしまう。
そのような状況の回避方法として、レコードそのものは残したまま無効化することでそのレコードは削除されたものとして扱うことができ、それをソフトデリート(論理削除)と呼びます。
テーブルイメージ
ソフトデリートを行うためにはdeleted_atカラムを追加する必要があります。
deleted_atに日付が登録されたレコードがソフトデリートされたものとなります。
例:社畜Mと社畜♀をソフトデリートする。
■usersテーブル
id | user_name | deleted_at |
---|---|---|
1 | SYACHIKU_S | null |
2 | SYACHIKU_M | 2025-02-17 01:11:11 |
3 | SYACHIKU_L | null |
4 | SYACHIKU_♀ | 2025-02-18 02:22:22 |
5 | SYACHIKU_MM | null |
モデルファイル設定内容
下記を追記することで、通常の物理削除がソフトデリートに切り替わります。
ソフトデリートされたデータは取得の際に無視されます(取得しない)。
// ソフトデリート利用を宣言
use Illuminate\Database\Eloquent\SoftDeletes;
class user extends Model
{
// ソフトデリート利用を再宣言
use SoftDeletes;
}
おまけ:データの扱い方をいくつか紹介
- データ取得のパターン
$userId = 1; // 条件に合致するレコードがソフトデリートされていない場合に取得 $ans = user::find($userId)->users // 条件に合致するレコードをソフトデリートされたかどうかを問わず取得 $ans = user::withTrashed()->find($userId)->users; // 条件に合致するレコードがソフトデリートされている場合に取得 $ans = user::onlyTrashed()->find($userId)->users; // ソフトデリートされていないレコードをすべて取得 user::get(); // ソフトデリートされたかどうかを問わずすべてのレコードを取得 user::withTrashed()->get(); // ソフトデリートされているレコードをすべて取得 user::onlyTrashed()->get();
- ソフトデリート状態を解除する(restore)
// idをキーにソフトデリート状態を解除 user::withTrashed()->where('id', 1)->restore(); // 複数id(複数レコード)のソフトデリート状態を解除 user::withTrashed()->whereIn('id', [1, 2, 3])->restore(); // idが存在しなかった場合の参照エラーを回避するやり方 $user = user::withTrashed()->find(1); $user->restore();
- データを物理削除する(forceDelete)
// idをキーに物理削除 user::withTrashed()->where('id', 1)->forceDelete(); // 複数id(複数レコード)を物理削除 user::withTrashed()->whereIn('id', [1, 2, 3])->forceDelete(); // idが存在しなかった場合の参照エラーを回避するやり方 $user = user::withTrashed()->find(1); $user->forceDelete();
まとめ
今回はlaravelの応用編ということで、個人的に便利だと思った機能のリレーションとソフトデリートについて解説しました‼
他にもlaravelには便利な書き方がたくさんあるので、laravelで開発をする際はぜひ活用してください‼