0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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

Posted at

過去実装への対応

以下で対応した複合キー利用のテーブルに対し、$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でデータ登録します。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?