Laravel で外部キー制約の onDelete / onUpdate をマイグレーション後に設定する + 論理削除でハマったこと


やりたいこと



  • CompanyEmployeeのように親子関係にあるテーブルを作成


  • employeeテーブルにcompany_idという外部キーを設定

  • その後、 onDelete onUpdateに対する挙動を設定し忘れたことに気づいたので後から設定をしたい


マイグレーションファイル


  • companies


timestamp_create_companies_table.php

class CreateCompanyTable extends Migration

{
/**
* Run the migrations.
*
* @return void
*/

public function up()
{
Schema::create('companies', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->timestamps();
$table->softDeletes();
});
}

/**
* Reverse the migrations.
*
* @return void
*/

public function down()
{
Schema::dropIfExists('company');
}
}



  • employee


timestamp_create_employee_table.php

class CreateEmployeeTable extends Migration

{
/**
* Run the migrations.
*
* @return void
*/

public function up()
{
Schema::create('employee', function (Blueprint $table) {
$table->increments('id');
$table->string('name')->nullable();
$table->integer('company_id')->unsigned();
$table->foreign('company_id')->references('id')->on('companies');
$table->timestamps();
$table->softDeletes();
});
}

/**
* Reverse the migrations.
*
* @return void
*/

public function down()
{
Schema::dropIfExists('employee');
}
}


マイグレーションは問題なくて、その後も色々なテーブルを追加していった後、onDelete / onUpdate の設定ができていないことに気づいて、「全部ロールバックするのも気が重いし、一部分だけ弄れないかな…」と逡巡して以下の方法を取った。


やったこと

あまりお行儀がよくないかもしれないが、 employeeテーブルのcompany_idだけを drop するマイグレーションファイルを作り、php artisan migrateを実行。

その後外部キー制約を再設定するマイグレーションファイルを作って、同様にphp artisan migrate


  • 外部キーを一旦Drop


timestamp_drop_foreign_key_from_employee.php

class DropForeignKeyFromEmployee extends Migration

{
/**
* Run the migrations.
*
* @return void
*/

public function up()
{
//外部キー制約を引き剥がす時は dropForeign('テーブル名'_'外部キー名'_foreign);
Schema::table('employee', function (Blueprint $table) {
$table->dropForeign('employee_company_id_foreign');
});
}
}


timestamp_reset_foreign_key_to_employee.php

public function up()

{
//子テーブルに対象レコードがある場合、親テーブルのレコード削除を禁止 ->onDelete('restrict');
//親テーブルのレコード更新は許可 ->onUpdate('cascade');
Schema::table('employee', function (Blueprint $table) {
$table->index('company_id');
$table->foreign('company_id')->references('id')->on('companies')
->onDelete('restrict')
->onUpdate('cascade');
});
}

/**
* Reverse the migrations.
*
* @return void
*/

public function down()
{
Schema::table('employee', function (Blueprint $table) {
$table->dropForeign('employee_company_id_foreign');
});
}



問題点

このやり方だとロールバックした時にDropForeignKeyFromEmployeeエラーで怒られる。なので、最初からなるべくテーブル作成時に onDelete / onUpdate の条件は慎重に考えて設定した方がいい。うまく後付けで設定できる方法があればコメントで教えていただきたいです。個人的に思いつくのがMySQLのコマンドで直接テーブルを書き換える手段だけなので…


その他ハマったこと

onDelete('restrict')を設定したあと、Laravel アプリケーションからCompany::destroyで実際に削除を試してみたところ、なんと制約に引っかからず普通に削除できてしまった…

どうやらuse SoftDeleteで論理削除をさせている場合、物理削除と違ってdeleted_atにタイムスタンプを書き込むだけなので制約をすり抜けてしまうらしい…。早めに知っておきたかった。

参考リンク

What if onDelete is restrict instead of cascade?

マイグレーションまとめ(Laravel5)