Laravel にはマイグレーションという DB(データベース)のバージョン管理的な機能があります。ソースコードに DB の変更を記述して、その差分を積み上げることによって再現性が確保された DB の構築を実現します。
このマイグレーションは主に次の様にスキーマビルダー、時々生SQLを使って作ることがほとんどです。
// ↓より引用
// @see https://readouble.com/laravel/9.x/ja/migrations.html
return new class extends Migration
{
/**
* マイグレーションの実行
*
* @return void
*/
public function up()
{
Schema::create('flights', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('airline');
$table->timestamps();
});
}
マイグレーションのソースコードはPHPで構成される以上、Laravelに備わっているテーブルとレコードについてのモデルである Eloquent を混ぜることもできます。Eloquent にはテーブルについての定義を一部記述するため、それをマイグレーション内で使いたくなる場合もあります。しかしながら、それは避けるべき方法です。その理由はマイグレーションの中で実行されるクエリが Eloquent のソースコードの変更によって変わり、マイグレーションで動くべきクエリと異なるクエリになりやすいためです。
この問題が起きるより具体的な例が次です。
マイグレーションを作った時で次の通りの users テーブルを表現するユーザーモデルのソースコードがあったとします。テーブル名が users、主キー名が id というテーブルです。
class User extends Model
{
/** @var string テーブル名 */
public $table = 'users';
/** @var string 主キー名 */
protected $primaryKey = 'id';
}
このモデルを使って次の様にマイグレーションで外部キー制約を追加します。
public function up();
{
// 投稿された記事のテーブルの owner_user_id によって ユーザー:ユーザーが投稿した記事 = 1:n の関係を表現する
Schema::table('posts', static function (Blueprint $table) {
$table->foreign('owner_user_id')
->references((new User())->getKeyName())
->on((new User())->getTable())
->onUpdate('cascade')
->onDelete('cascade');
});
}
その後、ユーザーIDのカラム名が次の様に変わったとします。
class User extends Model
{
/** @var string テーブル名 */
public $table = 'users';
/** @var string 主キー名 */
protected $primaryKey = 'user_id';
}
こうなるとモデル変更前は次の様なクエリが発行されていたところが
ALTER TABLE `posts` ADD CONSTRAINT `posts_owner_user_id_foreign` FOREIGN KEY (`owner_user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;
次の様になってしまいます。
ALTER TABLE `posts` ADD CONSTRAINT `posts_owner_user_id_foreign` FOREIGN KEY (`owner_user_id`) REFERENCES `users` (`user_id`) ON DELETE CASCADE ON UPDATE CASCADE;
マイグレーション実行時点では id であるべきところ、現在のユーザーIDのカラム名である user_id を使ってしまいます。こうなると存在しないカラムを参照にしたクエリが発行されることになり、マイグレーションが期待通りに動かなくなります。
例はカラム名ですが、定数やクエリビルダのグローバルスコープなどでも同様の問題が起こりえます。もし、マイグレーション内でクエリビルダを用いる必要がある場合は Eloqeunt を使ったクエリビルダではなく\DB::query()で使えるクエリビルダと stdClass を用いるべきです。
Laravel のマイグレーションは DB についての差分を集積し、それを取り扱う仕組みです。こういった差分を集積する機能が現在の DB 構造を表現するモデルに依存してしまうと差分が差分でなくなってしまい、異常動作につながります。例と題では Laravel にしていますが、これはフレームワーク、ライブラリ、言語問わず現在のデータベース構造を表現する機能と差分によって DB を構築する機能を持つ全てについて言えます。