概要
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" |