はじめに
ORM (Object Relational Mapper) = Eloquent は、Laravel を使う最強の利点といっても過言ではありません!
Laravel を採用する上では必ずマスターしたい機能ですね!
めちゃくちゃマスターするにゃ!
気を付けないといけないのは、Laravel の ORM は便利だけど、裏ではしっかりとクエリが流れているから、必ずどんなクエリが流れているかは確認してね!
Laravel 含めて世に出ているフレームワークは完ぺきではありません。
必ずパフォーマンス部分は意識する習慣はつけてね、クロたん・・・!
うるさいにゃ~~~。
環境
- php version
php -v PHP 7.4.33
- Laravel version
php artisan -V Laravel Framework 8.83.27
準備!!
記事のコマンドはすべて正常に動くか検証済みです。
かつ、最小限の構成をとっていますので、ぜひとも手を動かしながらやってみてください!
- migration 作成( DDL は1つのファイルにまとめます。)
php artisan make:migration create_tables
<?php use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; class CreateTables extends Migration { /** * Run the migrations. * * @return void */ public function up() { // userテーブルも使うが、デフォルトであるので記述しない // ブログテーブル Schema::create('blogs', function (Blueprint $table) { $table->id('blog_id'); $table->unsignedBigInteger('category_id'); $table->unsignedBigInteger('user_id'); $table->string('title'); $table->timestamps(); }); // カテゴリテーブル Schema::create('categories', function (Blueprint $table) { $table->id('category_id'); $table->string('category_name'); $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('blogs'); Schema::dropIfExists('categories'); } }
- migration 実行
php artisan migrate:fresh
※確実に実行できるように fresh を付けています。
- Model 作成
php artisan make:model Blog && php artisan make:model Category
- それぞれの Blogモデル Categoryモデル に対して、
primary key となるカラム名を $guarded に定義する。
▼Blogモデル
<?php namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; class Blog extends Model { use HasFactory; // ↓ここの部分(createメソッドで挿入できるようにする。) protected $guarded = ['blog_id']; // ↓プライマリキーを明示的に指定する。(後で説明) protected $primaryKey = 'blog_id'; /** * プライマリキーを返す(後で説明) * @return string */ public function get_primary_key(): string { return $this->primaryKey; } }
▼Categoryモデル
<?php namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; class Category extends Model { use HasFactory; // ↓ここの部分(createメソッドで挿入できるようにする。) protected $guarded = ['category_id']; // ↓プライマリキーを明示的に指定する。(後で説明) protected $primaryKey = 'category_id'; /** * プライマリキーを返す(後で説明) * @return string */ public function get_primary_key(): string { return $this->primaryKey; } }
▼Userモデル
<?php namespace App\Models; use Illuminate\Contracts\Auth\MustVerifyEmail; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; use Laravel\Sanctum\HasApiTokens; class User extends Authenticatable { use HasApiTokens, HasFactory, Notifiable; // ↓プライマリキーはデフォルトのIdを使用する。(標準搭載のテーブルはいじらない方がいい。) protected $primaryKey = 'id'; /** * The attributes that are mass assignable. * * @var array<int, string> */ protected $fillable = [ 'name', 'email', 'password', ]; /** * The attributes that should be hidden for serialization. * * @var array<int, string> */ protected $hidden = [ 'password', 'remember_token', ]; /** * The attributes that should be cast. * * @var array<string, string> */ protected $casts = [ 'email_verified_at' => 'datetime', ]; /** * プライマリキーを返す(後で説明) * @return string */ public function get_primary_key(): string { return $this->primaryKey; } }
Laravel ではテーブルのプライマリキーは暗黙的に [id] となっている。
しかし、実務において [id] では、テーブル構造が複雑になってくると、
カラム名の衝突 や なんのId? といった具合にわかりにくさが増してしまうので、明示的に指定してあげる。
また、後に説明する hasOne(引数1, 引数2, 引数3) の 引数2,3 が混乱を招きやすいので、
分かりやすいように get_primary_key() を記述しておきます。
- テストデータを tinker を用いて作成する
php artisan tinker
- ↓をコピペで流す!
\App\Models\User::create([ 'name' => 'ユーザー1', 'email' => 'hogehoge@example.com', 'password' => bcrypt('password'), ]); \App\Models\Category::create([ 'category_name' => 'カテゴリ1' ]); \App\Models\Blog::create([ 'category_id' => 1, 'user_id' => 1, 'title' => 'タイトル1', ]);
もちろん、Seeder でもいいんですが、そこの説明をすると長ったらしくなるので、tinker で代替。
tinker はこういうとき便利だにゃ~。
- Route 定義
Route::get('', [\App\Http\Controllers\IndexController::class, 'index']);
- Controller 作成
php artisan make:controller IndexController
本題: hasOne / belongsTo
hasOne / belongsTo の考え方は 1:1 です。
これらの違いは、
そのModelが持つデータは、対等関係で何と1:1 の関係を持っているのかを表すのが hasOne。
そのModelが持つデータは、なにの操作で生成されるのか、を表すのが belongsTo。
こう説明されても、実運用でどうあてはめていけばいいのかが分からないというのが、
分かりやすい具体例の落とし穴ではありますが、正直、慣れの部分が非常に大きいです。
上述の例で具体的にいうと、
Blog は Category を1つ持つことができ、そのデータ間で主従関係はないので hasOne。
Blog は User によって作成されるので、belongsTo。
次節から具体的なコードを見て、感覚をつかんでください!
hasOne
- \App\Models\Blog に以下を記載
public function category(): hasOne { $category_model = new \App\Models\Category(); # ↓リレーション先のモデル( Category )のプライマリキーが第2引数に来てる return $this->hasOne($category_model, $category_model->get_primary_key(), $this->primaryKey); }
必ず、namespace 下に
use Illuminate\Database\Eloquent\Relations\HasOne;
↑を記述してください。
- IndexController に以下を記載
public function index() { // blog_id = 1 を条件にまずデータを取得している。ここも重要。 $blog_data = \App\Models\Blog::find(1); // 取得してきた blogデータ の category_id を使ってリレーションを形成している $category_data = $blog_data->category; }
->category の部分が、Modelで定義した関数部分となる。
- ↓実際に流れているクエリを確認
$blog_data->category()->dd();
select * from `categories` where `categories`.`category_id` = 1 and `categories`.`category_id` is not null
categoriesテーブルから、category_id を条件に付与して取得してるんだけなんだな~。
belongsTo
- \App\Modes\Blog に以下を記載
public function user(): belongsTo { $user_model = new \App\Models\User(); # ↓リレーション先のモデル( User )のプライマリキーが第3引数に来てる return $this->belongsTo($user_model, 'user_id', $user_model->get_primary_key()); }
必ず、namespace 下に
use Illuminate\Database\Eloquent\Relations\BelongsTo;
↑を記述する!
※hasOne と belongsTo で、引数が逆になる。ここが厄介。。。
- IndexController に以下を記載
public function index() { // blog_id = 1 を条件にまずデータを取得している。ここが肝。 $blog_data = \App\Models\Blog::find(1); // 取得してきた blogデータ の user_id を使ってリレーションを形成している $user_data = $blog_data->user; }
->user の部分が、Modelで定義した関数部分となる。
- ↓実際に流れているクエリを確認
$blog_data->user()->dd();
select * from `users` where `users`.`id` = 1
さいごに
hasOne と belongsTo のリレーションを解説しました。
クエリの確認で dd() していたように、追加で↓のようにどんどん繋げられるので応用も効きます!
$blog_data->user()->where('name', 'ユーザー名')->get();
使わない選択肢はない!これを機会に、マスターしましょう。
当ブログでは、他にも hasMany / belongsToMany を紹介していますのでそちらもぜひご覧ください。
作成したファイルを削除 (不要ファイルが残るのが気持ち悪い人向け)※Linux コマンドで削除しています。
cd /path/to/project
※ /path/to/project には、artisanファイル が格納されているディレクトリパスを指定する。
- Controller 削除
rm app/Http/Controllers/IndexController.php
- Model 削除
find $(pwd)/app/Models/ -type f -not -name "User.php" | xargs rm
- migrationファイル 削除
find $(pwd)/database/migrations/ -name "*create_table*" | xargs rm
- Route定義は行削除(これは Linuxコマンド ではなく普通に削除)
Route::get('', [\App\Http\Controllers\IndexController::class, 'index']);
またね~~~~。
コメント