【Laravel リレーション】1対多・多対多 でテーブル結合のやり方

45101 ワード

Laravel で2つのテーブルを結合してデータを取ってくる場合の書き方についてよく忘れるのでやり方をまとめておきます。
今回は Laravel のバージョンは 8.65 を利用しています。

1対多 での Laravel リレーション

以下のように shops , areas テーブルが紐づいている場合を想定。

テーブルとデータ準備

マイグレーションファイルは以下の通り。

class CreateShopsTable extends Migration
{
    public function up()
    {
        Schema::create('shops', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->string('shop_name', 20);
            $table->unsignedBigInteger('area_id');  // 外部キー
            $table->timestamps();

            $table->foreign('area_id')->references('id')->on('areas');
        });
    }

    public function down()
    {
        Schema::dropIfExists('shops');
    }
}
class CreateAreasTable extends Migration
{
    public function up()
    {
        Schema::create('areas', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->string('name', 20);
            $table->integer('sort_no');
            $table->timestamps();
        });
    }

    public function down()
    {
        Schema::dropIfExists('areas');
    }
}

テスト用にシーダーでデータも準備する。

class ShopSeeder extends Seeder
{
    public function run()
    {
        DB::table('shops')->insert([
            [
                'id' => 1,
                'shop_name' => '高級パン屋',
                'area_id' => 1
            ],
            [
                'id' => 2,
                'shop_name' => '高級クロワッサン屋',
                'area_id' => 2
            ],
			      ・・・
class AreaSeeder extends Seeder
{
    public function run()
    {
        DB::table('areas')->insert([
            [
                'id' => 1,
                'name' => '東京',
                'sort_no' => 1
            ],
            [
                'id' => 2,
                'name' => '大阪',
                'sort_no' => 2
            ],
						・・・

DatabaseSeeder.php にクラスを指定。

public function run()
{
    $this->call(AreaSeeder::class);
    $this->call(ShopSeeder::class);
}

DB を空にしてマイグレート、シーディングを行うコマンドを実行すると準備OK。

php artisan migrate:fresh --seed

hasMany / belogsTo

モデルを作成する。Areas に 複数の Shops が紐づいているので hasMany を使う。

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Area extends Model
{
    use HasFactory;

    public function shops() {
        return $this->hasMany('App\Models\Shop');
    }
}

Shops は逆で belongsTo を使う。

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Shop extends Model
{
    use HasFactory;

    public function area() {
        return $this->belongsTo('App\Models\Area');
    }
}

データ取得

コントローラーで紐付けてデータを取得することができる。

class ShopController extends Controller
{
    public function index() {
        // 主 → 従
				$area_tokyo = Area::find(1)->shops->toArray();

        // 主 ← 従
        $shop = Shop::find(2)->area->name;

        dd($area_tokyo, $shop);
    }
}

出力結果。

array:2 [0 => array:5 ["id" => 1
    "shop_name" => "高級パン屋"
    "area_id" => 1
    "created_at" => null
    "updated_at" => null
  ]
  1 => array:5 ["id" => 4
    "shop_name" => "高級食パン屋"
    "area_id" => 1
    "created_at" => null
    "updated_at" => null
  ]
]
"大阪"

多対多 での Laravel リレーション

routes (路線) と shops (店舗) が中間テーブルを介して連携している場合を想定。

テーブルとデータ準備

マイグレーションは以下の通り。

public function up()
{
    Schema::create('routes', function (Blueprint $table) {
        $table->id();
        $table->string('name', 20);
        $table->integer('sort_no');
        $table->timestamps();
    });
}
public function up()
{
    Schema::create('shops', function (Blueprint $table) {
        $table->bigIncrements('id');
        $table->string('shop_name', 20);
        $table->unsignedBigInteger('area_id');
        $table->timestamps();

        $table->foreign('area_id')->references('id')->on('areas');
    });
}
public function up()
{
    Schema::create('route_shop', function (Blueprint $table) {
        $table->unsignedBigInteger('route_id');
        $table->unsignedBigInteger('shop_id');
        $table->primary(['route_id', 'shop_id']);

        $table->foreign('route_id')->references('id')->on('routes');
        $table->foreign('shop_id')->references('id')->on('shops');
    });
}

中間テーブルの route_shop では、routes / shops に対して外部キーを設定。

シーダーでデータも追加する。

public function run()
{
    DB::table('route_shop')->insert([
        [
            'route_id' => 1,
            'shop_id' => 1
        ],
        [
            'route_id' => 2,
            'shop_id' => 1
        ],
				・・・
public function run()
{
    DB::table('routes')->insert([
        [
            'id' => 1,
            'name' => '空港線',
            'sort_no' => 1
        ],
        [
            'id' => 2,
            'name' => '大牟田線',
            'sort_no' => 2
        ],
				・・・

DatabaseSeeder.php にクラスを追加して、マイグレートすればテーブル作成完了。

php artisan migrate:fresh --seed

belongsToMany

多対多の場合は belogsToMany を使ってリレーションを定義する。

class Route extends Model
{
    use HasFactory;

    public function shops() {
        return $this->belongsToMany('App\Models\Shops');
    }
}

※この時、中間テーブル名はデフォルトで2つのテーブルをアルファベット順で繋いだもの(route_shop)になるので、shop_route 等にしている場合は第二引数で指定する必要がある。

class Shop extends Model
{
    use HasFactory;

    ・・・

    public function routes() {
        return $this->belongsToMany('App\Models\Route');
    }
}

データ取得

コントローラー側で、店舗に紐づく路線の情報を取得することができる。

class ShopController extends Controller
{
    public function index() {
        // 多:多
        $shop_route = Shop::find(1)->routes()->get()->toArray();
        
        dd($shop_route);
    }
}

出力結果。

array:2 [0 => array:6 ["id" => 1
    "name" => "空港線"
    "sort_no" => 1
    "created_at" => null
    "updated_at" => null
    "pivot" => array:2 []
  ]
  1 => array:6 ["id" => 2
    "name" => "大牟田線"
    "sort_no" => 2
    "created_at" => null
    "updated_at" => null
    "pivot" => array:2 []
  ]
]

まとめ

今回は Laravel のリレーションを使う際の書き方について解説しました。

コードの書き方やテーブル構成については公式サイトとUdemyの教材を参考にしています。
公式ドキュメント Laravel 8.x Eloquent:リレーション
Udemy PHPからLaravelまで サーバーサイドをとことんやってみよう

紐づけてデータ取得するケースは非常に多いので似たような場面で今回の記事が参考になれば幸いです。