★中間テーブルのレコードの重複を避ける (Unique)

1. 中間テーブルに対して、FormRequest作成

<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class PlaylistTrackCreate extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            'playlist_id' => 'required | unique:playlist_track,playlist_id',
            'track_id'    => 'required',
        ];
    }

    /**
     * カスタムエラーメッセージを設定。
     * 
     * @return array
     */
    // "playlist idの値は既に存在しています。"
    public function messages()
    {
        return [
            'playlist_id.unique' 
                => '既にこのプレイリストに追加済みです。',
        ];
    }
}



2. Controller側の記述

(FatControllerになっているので、
ビジネスロジックをModel, Serviceに移す。)

class TrackController extends Controller
{

/**
     * 選択した曲を選択したプレイリストに所属させ、中間テーブルに保存する。
     * 
     * [METHOD - URI]
     * POST       | tracks/addplaylist
     * 
     * @param
     * @return Illuminate\Http\Request $request
     * @throws
     */
    public function addPlaylist(PlaylistTrackCreate $request)
    // public function addPlaylist(Request $request)
    {

        // 現在の選択中の曲を取得
        $track = Track::find($request->track_id);


        // 中間テーブル(playlist_track)にattachメソッドで保存
        $track->playlists()->attach(
            [ 'playlist_id' => $request->playlist_id ], 
            [ 'track_id'    => $track->id ]
        );

        $playlist = Playlist::find($request->playlist_id);

        return redirect()->route('playlists.show', [
            'playlist' => $playlist
        ]);
    }

}

3. Migration

class CreatePlaylistTrackTable extends Migration
{

    public function up()
    {
        Schema::create('playlist_track', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->bigInteger('track_id')->unsigned();
            $table->bigInteger('playlist_id')->unsigned();
            $table->timestamps();

            $table->foreign('track_id')
                    ->references('id')->on('tracks')
                    ->onDelete('cascade');
            $table->foreign('playlist_id')
                    ->references('id')->on('playlists')
                    ->onDelete('cascade');

            /**
             * 中間テーブルならば、複数カラムのユニークでないと
             * 一つしか登録できないのでdb側に複数ユニークをつける
             */
            $table->unique(['playlist_id', 'track_id']); 
            
            /**
             * [重複登録防止の実装自体は成功したので、下記エラーをハンドリングする必要あり。]
             * 
             * Illuminate\Database\QueryException
             * 
             * SQLSTATE[23000]: Integrity constraint violation: 
             * 1062 Duplicate entry '2-10' for key 'playlist_track_playlist_id_track_id_unique' 
             * (SQL: insert into `playlist_track` (`created_at`, `playlist_id`, `track_id`, `updated_at`) 
             * values (2020-10-01 19:49:36, 2, 10, 2020-10-01 19:49:36))
             * 
             * http://localhost/tracks/addplaylist?playlist_id=2&track_id=10
             */
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        /**
         * [トラブル解決の参考にした]
         * https://laracasts.com/discuss/channels/laravel/i-cant-drop-unique-index
         */
        Schema::disableForeignKeyConstraints('playlist_track', function (Blueprint $table) {
            $table->dropUnique('playlist_track_playlist_id_track_id_unique');
        }); 

        Schema::dropIfExists('playlist_track');
    }
}