過去実装への対応
以下で対応した複合キー利用のテーブルに対し、$incrementingプロパティを設定する処置をしましたが機能追加の際に別のエラーが発生しました。
複合キーを使って当分困ることはなかったのですが、マスタデータを管理する画面処理にて以下の問題が発生しました。
・関連データをコピーして新しいレコードとして登録する際、複合キーを使用したテーブル(Model)ではエラーが発生する
主に発生したエラー
Method Illuminate\Database\Eloquent\Collection::replicate does not exist.
array_key_exists(): Argument #1 ($key) must be a valid array offset type
実行していた処理
$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()でも不可)
対応
諦めてキャラクターIDとスキルIDの複合キーをやめて、単一キーの追加で対応することに。
テーブル構造を変えるためにマイグレーションファイルを作成して、単一キーの追加と、既存データにキーの値を設定する処理を追加します。
複合キーを削除する準備
まずは複合キーを削除するため制限の解除とプライマリキー用のカラム追加をします。
キャラクターIDだけ外部参照が付いていたので外しておきます。
新しく登録するunit_skill_idは、この時点ではまだプライマリキー属性をつけません。
既存データが存在するため、ここで付けてしまうと一意な値の設定ができずエラーになります。
カラムの追加だけして、一意な値を割り当てるところまでをこのファイルで実行します。
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の実行順が
1.unit_skill_idにプライマリ属性追加
2.外部参照解除
3.複合キー解除
の順番となり、プライマリキー重複のエラーが発生します。
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
あえてマイグレーションファイルを分けて、明示的に実行順を制御します。
プライマリキー属性の追加
あとはカラム変更処理で付け替え完了
Schema::table('unit_skill_info', function (Blueprint $table) {
$table->primary('unit_skill_id')->change();
});
どうしても複合キーにしたい場合は、自力で各カラムの値をオブジェクトに代入してModel::createでデータ登録します。