概要
Laravelでは、テーブル間のリレーションを設定することで、外部キーで関連付けられた複数のモデルを(外部キーを意識することなく)一貫して扱うことができる。
リレーションの種類と定義方法
リレーションはモデル定義で設定し、以下の3種類の何れかを設定する。
- hasOne
- 1対1のリレーションの親のモデルで設定し、親データに属する子データのモデルとidを指定する。これにより親データから子データやそのプロパティーの参照が可能になる。
- hasMany
- 1対nの親のモデルで設定し、親データに属する子データのモデルとidを指定する。これにより親データから子データのコレクション、要素インスタンスやそのプロパティーの参照が可能になる。
- belongsTo
- 1対1、1対nとも子のモデルで設定し、子データが属する親データを指す外部キーと親のモデルを指定する。これにより子データから親データやそのプロパティーが参照可能になる。
いずれについてもモデルでの記述方法は同じで、たとえばhasMany
を例にとると以下の様に書く。
1 2 3 4 5 |
class 親のモデル extends Model { public function 子の参照名() { return $this->hasMany('App\子のモデル'); } } |
子の参照名はモデル名のスモールケースで、hasOne
の場合は単数形、hasMany
の場合は複数形。
例えば親テーブルcustomers
のデータが子テーブルemails
のデータを1つだけ持つ場合は以下の様に書く。
1 2 3 4 5 |
class Customer extends Model { public function email() { return $this->hasOne('App\Email'); } } |
また、親テーブルcustomers
のデータが子テーブルorders
の複数のデータを持つ場合は以下の様に書く。
1 2 3 4 5 |
class Customer extends Model { public function orders() { return $this->hasMany('App\Order'); } } |
子テーブルのデータが親テーブルのデータに属することを設定するbelongsTo
では、メソッド名は単数形。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
class Email extends Model { public function customer() { return $this->belongsTo('App\Customer'); } } class Order extends Model { public function customer() { return $this->belongsTo('App\Customer'); } } |
リレーション設定後の参照方法
リレーションを設定すると、そのメソッド名と同じ名前のプロパティーによって親から子、子から親のモデルインスタンスを参照できるようになる。
先のCustomer
、Email
、Order
の場合、以下の様に参照する。
1 2 3 4 5 6 7 8 9 10 11 |
$customer->email // その顧客のemailアドレスがEmailインスタンスで得られる $customer->orders // その顧客の注文品群がOrderインスタンスの配列で得られる $email->customer // そのemailを持つCustomerインスタンスが得られる $email->customer // その注文をしたCustomerインスタンスが得られる |
子データが得られれば、以下の例の様にそのプロパティーも得ることができる。
1 2 3 4 5 6 7 8 |
$customer->email->email; // 顧客のEmailデータのemailプロパティー $customer->orders[0]->item; // 顧客の最初の注文データの注文品名 $order->customer->name; // 注文した顧客の名前 |
挙動確認
準備
設定
挙動確認のため、3つのモデルとテーブルを作成する。
Customer
- Auto Increment(AI)のid、顧客名、作成・更新日時を持つ
- e-mailアドレスを1つだけ持つ
- 複数の注文を持つ
- Email
- AIのid、e-mailアドレスを持つ顧客のid、アドレス、作成・更新日時を持つ
- Order
- AIのid、注文した顧客のid、注文品、作成・更新日時を持つ
モデル作成
artisanでCustomer
、Email
、Order
の3つのモデルを、マイグレーションファイルとともに作成する。
1 2 3 4 5 6 7 8 9 |
$ php artisan make:model Customer --migration Model created successfully. Created Migration: 2021_10_11_203918_create_customers_table $ php artisan make:model Email --migration Model created successfully. Created Migration: 2021_10_11_203937_create_emails_table $ php artisan make:model Order --migration Model created successfully. Created Migration: 2021_10_11_203951_create_orders_table |
timestampsの生成の抑制
本筋ではないが、ここではテーブル構造をシンプルにするため、デフォルトのtimestamps
の生成を抑制している。
マイグレーションファイル編集
マイグレーションファイルを編集し、各テーブルの要素を追加する。なおタイムスタンプを省略するため、各マイグレーションファイルに自動で記述される$table->timestamps()
は削除している。
customers
テーブルには顧客名を追加記述する。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
class CreateCustomersTable extends Migration { public function up() { Schema::create('customers', function (Blueprint $table) { $table->bigIncrements('id'); // 顧客名 $table->string('name'); }); } ........ } |
emails
テーブルには以下を追加記述する。
- 親の
customers
テーブルのid
を参照する外部キーcustomer_id
と、メールアドレスemail
customer_id
の外部キー設定
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
class CreateEmailsTable extends Migration { public function up() { Schema::create('emails', function (Blueprint $table) { $table->bigIncrements('id'); // メールアドレスを持っている顧客ID $table->unsignedBigInteger('customer_id'); // メールアドレス $table->string('email'); // 外部キー設定 $table->foreign('customer_id')->references('id')->on('customers') ->onDelete('cascade')->onUpdate('cascade'); }); } ........ } |
orders
には以下を追加記述する。
- 親の
customers
テーブルのidを参照する外部キーcustomer_id
と、注文品名item
customer_id
の外部キー設定
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
class CreateOrdersTable extends Migration { public function up() { Schema::create('orders', function (Blueprint $table) { $table->bigIncrements('id'); // 注文主の顧客ID $table->unsignedBigInteger('customer_id'); // 注文品名 $table->string('item'); // 外部キー設定 $table->foreign('customer_id')->references('id')->on('customers') ->onDelete('cascade')->onUpdate('cascade'); }); } ........ } |
マイグレーション
マイグレーションを実行する。
1 2 3 4 5 6 7 |
$ php artisan migrate Migrating: 2021_10_11_203918_create_customers_table Migrated: 2021_10_11_203918_create_customers_table (0.01 seconds) Migrating: 2021_10_11_203937_create_emails_table Migrated: 2021_10_11_203937_create_emails_table (0.04 seconds) Migrating: 2021_10_11_203951_create_orders_table Migrated: 2021_10_11_203951_create_orders_table (0.04 seconds) |
テーブル構造
生成された3つのテーブルは以下のとおり。
customers
テーブル
1 2 3 4 5 6 7 8 |
mysql> DESCRIBE customers; +-------+-----------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +-------+-----------------+------+-----+---------+----------------+ | id | bigint unsigned | NO | PRI | NULL | auto_increment | | name | varchar(255) | NO | | NULL | | +-------+-----------------+------+-----+---------+----------------+ 2 rows in set (0.00 sec) |
emails
テーブル。
1 2 3 4 5 6 7 8 9 |
mysql> DESCRIBE emails; +-------------+-----------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +-------------+-----------------+------+-----+---------+----------------+ | id | bigint unsigned | NO | PRI | NULL | auto_increment | | customer_id | bigint unsigned | NO | MUL | NULL | | | email | varchar(255) | NO | | NULL | | +-------------+-----------------+------+-----+---------+----------------+ 3 rows in set (0.00 sec) |
orders
テーブル。
1 2 3 4 5 6 7 8 9 |
mysql> DESCRIBE orders; +-------------+-----------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +-------------+-----------------+------+-----+---------+----------------+ | id | bigint unsigned | NO | PRI | NULL | auto_increment | | customer_id | bigint unsigned | NO | MUL | NULL | | | item | varchar(255) | NO | | NULL | | +-------------+-----------------+------+-----+---------+----------------+ 3 rows in set (0.00 sec) |
リレーション設定
リレーションを各モデルで設定する。なおタイムスタンプを省略するため、各モデルで$timestamps
をfalse
にセットする行を追加している。
Customer
モデルでは、1対1でEmail
を子に持つこと、1対nでOrder
を子に持つことを設定。
1 2 3 4 5 6 7 8 9 10 11 12 |
class Customer extends Model { public $timestamps = false; public function email() { return $this->hasOne('App\Email'); } public function orders() { return $this->hasMany('App\Order'); } } |
Email
モデルではCustomer
を親に持つことを設定。
1 2 3 4 5 6 7 8 |
class Email extends Model { public $timestamps = false; public function customer() { return $this->belongsTo('App\Customer'); } } |
Order
モデルではCustomer
を親に持つことを設定。
1 2 3 4 5 6 7 8 |
class Order extends Model { public $timestamps = false; public function customer() { return $this->belongsTo('App\Customer'); } } |
テストデータ作成
tinkerでテストデータを作成し、データベースに登録する。
Customer
まずCustomer
のデータを登録する。
1 2 3 4 5 6 7 8 9 10 |
$ php artisan tinker Psy Shell v0.10.8 (PHP 7.3.29 — cli) by Justin Hileman >>> $customer = new Customer(); [!] Aliasing 'Customer' to 'App\Customer' for this Tinker session. => App\Customer {#3380} >>> $customer->name = 'customer1'; => "customer1" >>> $customer->save(); => true ........ |
3人の顧客データを登録。
1 2 3 4 5 6 7 8 9 |
mysql> SELECT * FROM customers; +----+-----------+ | id | name | +----+-----------+ | 1 | customer1 | | 2 | customer2 | | 3 | customer3 | +----+-----------+ 3 rows in set (0.00 sec) |
Email
のデータを、Customer
の各データに関連付けながら登録する。ここではcustomer1
とcustomer2
に紐づくデータを登録し、customer3
のアドレスは未登録とする。
1 2 3 4 5 6 7 8 9 10 |
>>> $email = new Email(); [!] Aliasing 'Email' to 'App\Email' for this Tinker session. => App\Email {#4101} >>> $email->customer_id = 1; => 1 >>> $email->email = 'customer1@mail.com'; => "customer1@mail.com" >>> $email->save(); => true ........ |
登録後のテーブルは以下のとおり。
1 2 3 4 5 6 7 8 |
mysql> SELECT * FROM emails; +----+-------------+--------------------+ | id | customer_id | email | +----+-------------+--------------------+ | 1 | 1 | customer1@mail.com | | 2 | 2 | customer2@mail.com | +----+-------------+--------------------+ 2 rows in set (0.00 sec) |
Order
Order
のデータを、Customer
の各データに関連付けながら登録する。ここではcustomer1
に1つの、customer2
に2つの注文があり、customer3
からの注文はない状態とする。
1 2 3 4 5 6 7 8 9 10 11 12 |
>>> $order = new Order(); [!] Aliasing 'Order' to 'App\Order' for this Tinker session. => App\Order {#4313} >>> $order->id = 1; => 1 >>> $order->customer_id = 1; => 1 >>> $order->item = 'screw'; => "screw" >>> $order->save(); => true ........ |
登録後のテーブルは以下のとおりで、customer1
はネジのオーダー1つ、customer2
はゴムシートとプラスチック板で2つのオーダー。
1 2 3 4 5 6 7 8 9 |
mysql> SELECT * FROM orders; +----+-------------+---------------+ | id | customer_id | item | +----+-------------+---------------+ | 1 | 1 | screw | | 2 | 2 | rubber sheet | | 3 | 2 | plastic plate | +----+-------------+---------------+ 3 rows in set (0.00 sec) |
リレーションを使った操作
親データを取得
まず3人の顧客のデータを取得する。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
>>> $customer1 = Customer::find(1); => App\Customer {#4322 id: 1, name: "customer1", } >>> $customer2 = Customer::find(2); => App\Customer {#4315 id: 2, name: "customer2", } >>> $customer3 = Customer::find(3); => App\Customer {#4323 id: 3, name: "customer3", } |
親からhasOneを取得
それぞれの顧客データから、hasOne
で関連付けられたemail
のインスタンスを取得する。$customer3
のemail
は未登録なのでnull
となる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
>>> $customer1->email; => App\Email {#4328 id: 1, customer_id: 1, email: "customer1@mail.com", } >>> $customer2->email; => App\Email {#4329 id: 2, customer_id: 2, email: "customer2@mail.com", } >>> $customer3->email; => null |
親からhasManyを取得
それぞれの顧客データから、hasMany
で関連付けられたorder
のデータを取得する。
hasMany
リレーションのデータは配列で得られる- データが1つの場合は要素数1の配列
- 子データがない場合は空の配列
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
>>> $customer1->orders; => Illuminate\Database\Eloquent\Collection {#4335 all: [ App\Order {#4333 id: 1, customer_id: 1, item: "screw", }, ], } >>> $customer2->orders; => Illuminate\Database\Eloquent\Collection {#4324 all: [ App\Order {#4338 id: 2, customer_id: 2, item: "rubber sheet", }, App\Order {#4341 id: 3, customer_id: 2, item: "plastic plate", }, ], } >>> $customer3->orders; => Illuminate\Database\Eloquent\Collection {#4344 all: [], } |
hasMany
の場合、子データは配列で得られるので、要素指定やforeachなどで個々のデータを取り出す。
1 2 3 4 5 6 |
>>> $customer1->orders[0] => App\Order {#4333 id: 1, customer_id: 1, item: "screw", } |
子データのプロパティーの取得
hasOne
やhasMany
の子データが親データから得られれば、そのプロパティーを得ることもできる。
1 2 |
>>> $customer1->orders[0]->item; => "screw" |
hasOneの子から親を取得
hasOne
のEmail
が属するCustomer
を取得する例。ここでは変数を介さず、生成したインスタンスから直接プロパティーを参照している。
1 2 3 4 5 6 7 8 9 10 |
>>> Email::find(1)->customer; => App\Customer {#4346 id: 1, name: "customer1", } >>> Email::find(2)->customer; => App\Customer {#4349 id: 2, name: "customer2", } |
hasManyの子から親を取得
hasMany
で関連付けられた子のOrder
から親のCustomer
を取得する例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
>>> Order::find(1)->customer; => App\Customer {#4350 id: 1, name: "customer1", } >>> Order::find(2)->customer; => App\Customer {#4352 id: 2, name: "customer2", } >>> Order::find(3)->customer; => App\Customer {#4354 id: 2, name: "customer2", } |
親データのプロパティーの取得
子データから親データが得られれば、そのプロパティーも得ることができる。
1 2 |
>>> Email::find(1)->customer->name; => "customer1" |