PHP

Laravel 中間テーブルを利用した多対多のリレーション

Laravel

2千分後の私へ。

 

ポイント

 

App/Http/Models/Job.php

<?php

namespace App\Http\Models;

use App\User;
use Illuminate\Database\Eloquent\Model;

class Job extends Model
{
    public function users()
    {
        return $this->belongsToMany('App\Http\Models\Users',
                                    'user_job',
                                    'job_id',
                                    'user_id');
    }
  • 第1引数:リレーション先のモデル名
  • 第2引数:リレーション先のテーブル名
  • 第3引数:自モデルの主キー
  • 第4引数:相手モデルの主キー

 

App/Http/Models/User.php

<?php

namespace App\Http\Models;

use Illuminate\Database\Eloquent\Model;

class Users extends Model
{

    public function departs()
    {
        return $this->belongsToMany('App\Http\Models\job',
                                    'user_job',
                                    'user_id',
                                    'job_id');
    }

こちらも引数を同じにします。

あとはuser_jobの中間テーブルを作成すれば良い。

 

指定したjob_idを持つユーザの名前とidを取得する例

$jobUsers = $job->find($job_id)->users()->pluck('name', 'id')

 

 

以下ゆっくり解説🐱✨

 

テーブルはこんな感じ

 

users

user_id role name email create_at updated_at
1 1 yuu yuu@example.net 2019-12-29 03:18:05 2019-12-29 03:18:05

 

devices

udid device_name create_at updated_at
1 windows_phone 2019-12-29 03:25:09 2019-12-29 03:25:09
2 ios 2019-12-29 03:18:05 2019-12-29 03:18:05
3 android 2019-12-29 03:25:09 2019-12-29 03:25:09

 

 

user_device_chains

user_id udid
1 2
1 3

 

中間テーブルのマイグレーションファイルの注意

 

 

user_device_chainsテーブル定義ファイル

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\DB;

class CreateUserDeviceChainsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    
        Schema::create('user_device_chains', function (Blueprint $table) {
            $table->unsignedBigInteger('user_id')->nullable(true)->comment('users.user_id - 会員情報id');
            $table->string('udid')->nullable(true)->comment('devices.udid - デバイスid');

            $table->foreign('user_id')
                ->references('user_id')
                ->on('users')
                ->onDelete('cascade');
            $table->foreign('udid')
                ->references('udid')
                ->on('devices')
                ->onDelete('cascade');

            $table->primary(['user_id', 'udid']);
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('user_device_chains');
    }
}

 

ポイント① BigIncrementで生成されたid列はunsignedBigIntegerで指定する

$table->unsignedBigInteger('user_id')->nullable(true)->comment('users.user_id - 会員情報id');
$table->string('udid')->nullable(true)->comment('devices.udid - デバイスid');

 

 

ポイント② 外部キー制約

$table->foreign('user_id')
    ->references('user_id')
    ->on('users')
    ->onDelete('cascade');
$table->foreign('udid')
    ->references('udid')
    ->on('devices')
    ->onDelete('cascade');

 

ポイント③ 複合主キーの設定

$table->primary(['user_id', 'udid']);

これを設定しないとパフォーマンスが落ちる

 

users

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\DB;

class CreateUsersTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('users', function (Blueprint $table) {
            $table->bigIncrements('user_id')->comment('会員情報テーブル主キー');
            $table->integer('user_role_id')->comment('会員種別');
            $table->string('user_name')->nullable(true)->comment('会員名');
            $table->string('email')->nullable(true)->comment('email');
            $table->timestamp('created_at')->nullable()->comment('作成日時');
            $table->timestamp('updated_at')->nullable()->comment('更新日時');

            $table->index('email');
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('users');
    }
}

 

ポイント① whereで検索されるものはindexをつける

$table->index('email');

 

devices

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\DB;


class CreateUserDevicesTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('devices', function (Blueprint $table) {
            $table->string('udid')->comment('devices.udid devicesテーブル主キー');
            $table->string('device_name')->comment('ツール名');
            $table->timestamp('created_at')->nullable()->comment('作成日時');
            $table->timestamp('updated_at')->nullable()->comment('更新日時');

            $table->index('udid');
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('devices');
    }
}

 

 

 

 

 

リレーション定義

 

Models/User.php

<?php

namespace App\Models;

use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Tymon\JWTAuth\Contracts\JWTSubject;

class User extends Authenticatable implements JWTSubject
{
    use Notifiable;

    protected $carbon;
    protected $now;

    protected $primaryKey = 'user_id';

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $guarded = [
    ];

    /**
     * The attributes that should be hidden for arrays.
     *
     * @var array
     */
    protected $hidden = [
        'password', 'remember_token',
    ];

    public function devices()
    {
        return $this->belongsToMany('App\Models\Device',
                                    'user_device_chains',
                                    'user_id',
                                    'udid');
    }

    public function userRoles()
    {
        return $this->belongsTo(UserRole::class, 'user_role_id', 'user_role_id');
    }

・・・

 

 

 

Models/Devices.php

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Device extends Model
{

    protected $primaryKey = 'udid';

    protected $guarded = [
    ];

    public function users()
    {
        return $this->belongsToMany('App\Models\User',
                                    'user_device_chains',
                                    'udid',
                                    'udid');
    }
}

 

 

 

 

Tinkerで確認

 

Userの確認

bash-5.0# php artisan tinker
Psy Shell v0.9.12 (PHP 7.3.11 — cli) by Justin Hileman


>>> use App\Models

>>> User::find(1)

[!] Aliasing 'User' to 'App\Models\User' for this Tinker session.
=> App\Models\User {#3089
     user_id: 1,
     user_role_id: 1,
     user_name: yuu,
     email: yuu@example.net
   }

 

 

Devicesから

>>> User::find(1)->devices
=> Illuminate\Database\Eloquent\Collection {#3101
     all: [
       App\Models\Device {#3105
         udid: "2",
         device_name: "ios",
         created_at: "2019-12-29 03:18:05",
         updated_at: "2019-12-29 03:18:05",
         pivot: Illuminate\Database\Eloquent\Relations\Pivot {#3098
           user_id: 1,
           udid: "1",
         },
       },
       App\Models\Device {#3102
         udid: "3",
         device_name: "android",
         created_at: "2019-12-29 03:25:09",
         updated_at: "2019-12-29 03:25:09",
         pivot: Illuminate\Database\Eloquent\Relations\Pivot {#3089
           user_id: 1,
           udid: "3",
         },
       },
     ],
   }

 

リレーション先の値を利用した検索

 

iosを持っているかつ、user_role_idが1のuserを検索

$device_name = 'ios'
$user_role_id = 1;

$user = User::whereHas('devices', function($query) {
        $query->where('devices.name', $device_name);
    })->where('users.user_role_id', $user_role_id)
       ->get();

 

  1. whwreHasの中のfunction() {$query->where(‘devices.name’, $device_name);}で結合条件を指定してjoinさせて、
  2. 結合したデータに対して最後にwhere(‘users.user_role_id’, $user_role_id)で行を絞り混み

 

 

中間テーブルにデータを挿入する時はattach()を利用する

 

            DB::beginTransaction();
            try {

                $user = new User();
                $user->user_name = $request->user_name;
                $user->email = $request->email;
                $user->password = $request->password;
                $user->save();

                $user_id = $user->user_id;
                $udid = $request->udid;
                $device_name = $request->device_name;

                $userDevice = new Device();
                $userDevice->timestamps = false;
                Device::create([
                    'udid' => $udid,
                    'device_name' => $device_name
                ]);

                $user->devices()->attach(
                    ['user_id' => $user_id],
                    ['udid' => $udid],
                    ['created_at' => $this->now],
                    ['updated_at' => $this->now]
                );

                DB::commit();
                return response()->json(config('mail.message.add_user_success'));

            } catch (\Exception $e) {
                DB::rollback();
                Log::error('WEB /users/me/verify - Class ' . get_class() . ' - PDOException Error. Rollback was executed.' . $e->getMessage());

                return response()->json(config('error.databaseTransactionRollback'));
            }

 

外部キー制約をかけているので順番があります。

  1. usersテーブルにレコード挿入
  2. devicesテーブルにレコード挿入
  3. 中間テーブルにレコード挿入

 

Insertしたレコードのidを知りたい時

 

save()を行ってからプロパティで取得する

$instance->save();
$instance->id;

 

usersテーブルにレコードを挿入した時の例

$user = new User();
$user->user_name = $request->user_name;
$user->email = $request->email;
$user->password = $request->password;
$user->save();

$user_id = $user->user_id;
echo $user_id; // 2

usersテーブルのidのカラムはuser_idなので合わせて対応しています。

 

中間テーブルのカラムを取得する

Laravel リレーション 中間テーブルのカラム取得 withPivot()

 

 

 

Amazonおすすめ

iPad 9世代 2021年最新作

iPad 9世代出たから買い替え。安いぞ!🐱 初めてならiPad。Kindleを外で見るならiPad mini。ほとんどの人には通常のiPadをおすすめします><

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)