Laravelのカラム変更マイグレーション:ネイティブ change() と doctrine/dbal の使い分け
概要
Laravel のマイグレーションでは、「カラムの型を変えたい」「NULL 許可を切り替えたい」「カラム名をリネームしたい」という変更が頻繁に発生します。新規テーブルの作成と違い、既存カラムを変更する操作は、歴史的に doctrine/dbal パッケージに依存していました。
しかし Laravel 11 でカラム変更がフレームワークネイティブに実装され、現行の 12・13 でもその挙動が続いています。つまり今は追加パッケージなしで ->change() や ->renameColumn() が使えます。doctrine/dbal が必要なのは Laravel 10 以前を保守している場合に限られます。
本記事では、ネイティブのカラム変更を主役に据えつつ、doctrine/dbal が必要だった旧世代の扱い、そして今も変わらない落とし穴までをハンズオン形式で解説します。
環境
- PHP 8.3 以上
- Laravel 12.x / 13.x(カラム変更がネイティブ、
doctrine/dbal不要の世代) - 比較対象として Laravel 10.x 以前(
doctrine/dbalが必要な世代) - データベース: MySQL 8.0(PostgreSQL でも基本は同様)
発生した問題
現行の Laravel(11 以降)では、次のマイグレーションは追加パッケージなしでそのまま動きます。
public function up(): void
{
Schema::table('users', function (Blueprint $table) {
$table->string('name', 100)->change();
});
}
一方、同じコードを Laravel 10 までの環境で doctrine/dbal を入れずに実行すると、次のエラーになります。
Changing columns for table "users" requires Doctrine DBAL. Please install the doctrine/dbal package.
->change() や ->renameColumn() のような「既存カラムを対象にする操作」は、Laravel 内部でスキーマ差分を解釈する必要があり、Laravel 10 以前はその処理を doctrine/dbal に委ねていたためです。
原因
Laravel の Blueprint は、新規カラムの作成・追加の SQL は自前で生成できます。しかし、既存カラムの定義を読み取り、変更後との差分から ALTER TABLE ... MODIFY を組み立てる処理は、DB ごとの方言差が大きく実装コストが高い領域です。
Laravel 10 以前は、この「既存スキーマの読み取りと差分生成」を doctrine/dbal のスキーマ抽象化レイヤに任せていました。dbal はカラム変更時だけ呼ばれる裏方だったわけです。Laravel 11 でこの処理がフレームワーク本体に取り込まれ、SQLite や旧来の MySQL を含む全ドライバでネイティブに変更・リネーム・削除ができるようになりました。
対応方法
1. 現行(Laravel 11/12/13)はそのまま change() が使える
現行世代では追加インストールは不要です。マイグレーションに ->change() を書くだけで動きます。すでに doctrine/dbal を依存に入れている場合は、Composer から外して構いません。
2. 型・長さ・NULL 許可を変更する
Schema::table('users', function (Blueprint $table) {
// VARCHAR(255) → VARCHAR(100)
$table->string('name', 100)->change();
// NOT NULL → NULL 許可
$table->string('phone')->nullable()->change();
// 型変更(string → text)
$table->text('bio')->change();
});
3. カラムをリネームする
Schema::table('users', function (Blueprint $table) {
$table->renameColumn('bio', 'profile');
});
4. Laravel 10 以前を保守している場合は doctrine/dbal を入れる
旧世代のプロジェクトで ->change() や ->renameColumn() を使うには、従来どおり doctrine/dbal が必要です。
composer require doctrine/dbal
本番でマイグレーションを流すなら通常の依存として入れておきます。新規・現行世代のプロジェクトではこの手順は不要です。
実装例
ロールバックまで含めた、実運用に近いマイグレーションの例です。現行世代ならそのまま、Laravel 10 以前なら doctrine/dbal を入れた上で動きます。
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration {
public function up(): void
{
Schema::table('articles', function (Blueprint $table) {
$table->longText('body')->change();
$table->boolean('is_published')->default(false)->nullable(false)->change();
$table->renameColumn('thumbnail', 'thumbnail_url');
});
}
public function down(): void
{
Schema::table('articles', function (Blueprint $table) {
$table->renameColumn('thumbnail_url', 'thumbnail');
$table->boolean('is_published')->default(false)->change();
$table->text('body')->change();
});
}
};
確認方法
マイグレーション前後でスキーマが意図どおり変わったかを確認します。
php artisan migrate php artisan db:show --table=articles php artisan migrate:rollback --step=1
MySQL クライアントから直接確認する場合:
SHOW CREATE TABLE articles;
注意点
変更時は「残したい属性」をすべて再指定する
->change() は、呼び出しで指定した定義でカラムをまるごと置き換えます。既存の属性は引き継がれません。NULL 許可だけ変えたつもりで長さやデフォルトを書き忘れると、それらが初期状態に戻ってしまうことがあります。この挙動はネイティブ化された Laravel 11 以降でより明確になり、12・13 でも同じです。
// NG: nullable だけ付けたいが、長さやデフォルトが落ちる可能性がある
$table->string('name')->nullable()->change();
// OK: 残したい属性をすべて明示する
$table->string('name', 100)->default('')->nullable()->change();
変更前に SHOW CREATE TABLE で現在の完全な定義を控えてから書くのが安全です。
enum など扱いにくい型に注意
enum は Laravel のスキーマ変更が正しく扱えないことがあり(doctrine/dbal も同様に enum を解釈できません)、同じテーブルに enum カラムがあるだけで change() が例外になる場合があります。この場合は生 SQL で対応します。
DB::statement("ALTER TABLE users MODIFY status ENUM('active','inactive','banned') NOT NULL DEFAULT 'active'");
dbal が必要なのは Laravel 10 以前だけ
カラム変更のネイティブ化は Laravel 11 で入り、12・13 でも引き続き有効です。したがって現行世代では doctrine/dbal は不要で、必要になるのは Laravel 10 以前を保守しているケースに限られます。ただし「変更時に属性をすべて再指定する必要がある(省略した属性は保持されない)」点は dbal の有無にかかわらず共通なので、旧世代から現行世代へ上げる際は、既存の change() マイグレーションが属性を省略していないか必ず見直してください。
まとめ
- Laravel 11 以降(現行の 12・13 を含む)は
doctrine/dbalなしでカラム変更・リネームができる doctrine/dbalが必要なのは Laravel 10 以前を保守している場合だけ->change()は属性を引き継がないため、残したい定義をすべて再指定するenumは扱いにくく、生 SQL(DB::statement)に逃がすのが定石
「カラムを変えるだけ」の作業ほど、バージョン差と属性の取りこぼしで事故りやすい領域です。移行前に現在の定義を控え、ロールバックまで通して検証する——遠回りに見えて、これが結局いちばん安全で速い進め方です。
