【Laravel】複合キーで他テーブルとつながっているテーブルのリレーションをEloquentで定義する

 ざっくりいうと次のように通常のリレーションにWHEREを付け足して複合キーとして十分なカラム指定をすればOKです。

    public function addresses()
    {
        return $this->hasMany(Address::class, 'user_id', 'user_id')
                    ->where('addresses.service_id', $this->service_id);
    }

 LaravelにはEloquentというあるテーブルについてのSQLを扱える機能があります。これは基本的に主キーが1カラムだけのテーブルを前提としており次のような複合キーのテーブルを扱う時にしばしば苦労します。

CREATE TABLE service_users  (
    service_id INT,
    user_id INT,
    username VARCHAR(255),
    PRIMARY KEY (service_id, user_id)
);
INSERT INTO service_users  (service_id, user_id, username) VALUES (1, 1, 'UserA');
INSERT INTO service_users  (service_id, user_id, username) VALUES (1, 2, 'UserB');
INSERT INTO service_users  (service_id, user_id, username) VALUES (1, 3, 'UserC');
INSERT INTO service_users  (service_id, user_id, username) VALUES (2, 1, 'UserD');
INSERT INTO service_users  (service_id, user_id, username) VALUES (2, 2, 'UserE');
INSERT INTO service_users  (service_id, user_id, username) VALUES (2, 3, 'UserF');
INSERT INTO service_users  (service_id, user_id, username) VALUES (3, 1, 'UserG');
INSERT INTO service_users  (service_id, user_id, username) VALUES (3, 2, 'UserH');
INSERT INTO service_users  (service_id, user_id, username) VALUES (3, 3, 'UserI');
SELECT * FROM service_users  WHERE (service_id, user_id) IN (
    (1, 1),
    (2, 3) 
);
+------------+---------+----------+
| service_id | user_id | username |
+------------+---------+----------+
|          1 |       1 | UserA    |
|          2 |       3 | UserF    |
+------------+---------+----------+

 複合主キーが現れるデータベースでは複数のカラム同士によるリレーションの定義もしばしば現れます。例えば上記のサービスユーザー1レコードが複数のアドレス帳を持つとする場合、次のようになります。

CREATE TABLE addresses (
    id INT AUTO_INCREMENT PRIMARY KEY,
    service_id INT,
    user_id INT,
    address VARCHAR(255),
    FOREIGN KEY (service_id, user_id) REFERENCES service_users(service_id, user_id)
);
INSERT INTO addresses (service_id, user_id, address) VALUES (1, 1, '浜松市中央区和地山');
INSERT INTO addresses (service_id, user_id, address) VALUES (1, 1, '浜松市中央区相生町');
INSERT INTO addresses (service_id, user_id, address) VALUES (1, 2, '浜松市天竜区佐久間町');
INSERT INTO addresses (service_id, user_id, address) VALUES (2, 3, '浜松市浜名区油一色');

 アドレス帳固有のデータがあり、それがどのサービスユーザーが所有しているかをサービスユーザーの主キーで示している形です。

 Laravelには複数のレコードのつながりを表現するリレーションという機能があります。

Eloquent:リレーション 10.x Laravel

 リレーションは次のようにカラムをつながる元と先で一つづつ指定することで用いることができます。

return $this->hasOne(Phone::class, 'foreign_key', 'local_key');

 複合キーならば一見次のように使うことができそうですが実はできません。配列を文字列に変換することはできません、というエラーになります。

    public function addresses()
    {
        return $this->hasMany(Address::class, ['user_id', 'service_id'],[ 'user_id', 'service_id']);
    }

 これを解決する方法として次のようにWHEREを追加することができます。

    public function addresses()
    {
        return $this->hasMany(Address::class, 'user_id', 'user_id')
                ->where('service_id', $this->service_id);
    }

 この様にモデル内のリレーションとして定義したメソッドにWHEREを付けると、そのリレーションを呼び出した時に毎回Laravelが自動でそのWHEREを付けてくれます。

 LaravelのEloquentは複合キーを前提としておらず、少なくない機能がそのまま使えません。都度対策していい感じのtraitを作ったり同じ問題で悩む他の人の解決策を参考にするのもいいです。

>株式会社シーポイントラボ

株式会社シーポイントラボ

TEL:053-543-9889
営業時間:9:00~18:00(月〜金)
住所:〒432-8003
   静岡県浜松市中央区和地山3-1-7
   浜松イノベーションキューブ 315
※ご来社の際はインターホンで「316」をお呼びください

CTR IMG