【Laravel】複合キーデータのコピー処理への対応

# 過去実装への対応

以下で対応した複合キー利用のテーブルに対し、$incrementingプロパティを設定する処置をしましたが機能追加の際に別のエラーが発生しました。

https://qiita.com/mijinko88/items/bef0da2b746b754a0d9a

複合キーを使って当分困ることはなかったのですが、マスタデータを管理する画面処理にて以下の問題が発生しました。

 ・関連データをコピーして新しいレコードとして登録する際、複合キーを使用したテーブル（Model）ではエラーが発生する

 ## 主に発生したエラー
 ```
 Method Illuminate\Database\Eloquent\Collection::replicate does not exist.
 ```

 ```
array_key_exists(): Argument #1 ($key) must be a valid array offset type
 ```

## 実行していた処理
``` PHP
$targetSkill = Skill::findOrFail($copySkillId);
if(isset($targetSkill)){
    $createData = $targetSkill->replicate();
    $createData->skill_id = $skillId;
    $createData->save(); 
}
//ここまではOK。targetSkillはCollection|Model|Skill|Skill[]という型になる

//基本1行分のみ処理される想定.whereだとコレクションで返されるのでループが必要
$targetUnitSkill= 
UnitSkill::where(['skill_id'=>$copySkillId,"character_id"=>$copyCharacterId])->first();

//★これで取得した結果、targetUnitSkillはCollection|UnitSkillになり
//replicateが呼び出せなくなる
foreach ($targetUnitSkill as $unitSkill) {
    $createUnitData = $targetUnitSkill->replicate();
    $createUnitData->skill_id = $skillId;
    $createUnitData->character_id = $characterId;
    $createUnitData->save();
}
```

二つ目のUnitSkillというテーブルのデータをコピーするときにエラーが発生します。
はじめはSkillと同じようにfindOrFailでコピー元データを取得しようとしましたが複合キーではエラーとなるため、whereで条件指定、firstで一意な値を取るように試しましたが上述のエラーとなります。
（get()やfirtWhere()でも不可）

https://qiita.com/sgrs38/items/2c3ccc2db6f55d2f5583

# 対応
諦めてキャラクターIDとスキルIDの複合キーをやめて、単一キーの追加で対応することに。
テーブル構造を変えるためにマイグレーションファイルを作成して、単一キーの追加と、既存データにキーの値を設定する処理を追加します。

## 複合キーを削除する準備
まずは複合キーを削除するため制限の解除とプライマリキー用のカラム追加をします。
キャラクターIDだけ外部参照が付いていたので外しておきます。

新しく登録するunit_skill_idは、この時点ではまだプライマリキー属性をつけません。
既存データが存在するため、ここで付けてしまうと一意な値の設定ができずエラーになります。

カラムの追加だけして、一意な値を割り当てるところまでをこのファイルで実行します。

```PHP
public function up()
    {
        Schema::table('unit_skill_info', function (Blueprint $table) {
            // 既存の複合キーを削除
            $table->dropIndex('unit_skill_info_skill_id_foreign');
            $table->dropForeign('unit_skill_info_character_id_foreign');
            // 新しい単一のキーを作成
            $table->integer('unit_skill_id')->first();
        });

        // 既存のデータに値を設定
        $unitSkills = DB::table('unit_skill_info')->get();
        $id = 1; // 初期値
        foreach ($unitSkills as $unitSkill) {
            DB::table('unit_skill_info')
                ->where('skill_id', $unitSkill->skill_id)
                ->where('character_id', $unitSkill->character_id)
                ->update(['unit_skill_id' => $id]);
            $id++; // 次の値を設定する
        }
    }
```

一意な値割り当てた後にもう一度Schema::tableでプライマリキー属性つければいいんじゃないの？と思いますが、「php artisan migrate --pretend」で確認するとSQLの実行順が
１．unit_skill_idにプライマリ属性追加
２．外部参照解除
３．複合キー解除
の順番となり、プライマリキー重複のエラーが発生します。
```
php artisan migrate --pretend
AlterTableUnitSkillInfoInfo: ALTER TABLE unit_skill_info CHANGE unit_skill_id unit_skill_id INT UNSIGNED AUTO_INCREMENT NOT NULL   
AlterTableUnitSkillInfoInfo: alter table `unit_skill_info` drop foreign key `unit_skill_info_character_id_foreign`
AlterTableUnitSkillInfoInfo: alter table `unit_skill_info` drop primary key
```
あえてマイグレーションファイルを分けて、明示的に実行順を制御します。

## プライマリキー属性の追加
あとはカラム変更処理で付け替え完了
``` PHP
 Schema::table('unit_skill_info', function (Blueprint $table) {
            $table->primary('unit_skill_id')->change();
        });
```

どうしても複合キーにしたい場合は、自力で各カラムの値をオブジェクトに代入してModel::createでデータ登録します。