Урок 05

Изменение существующих таблиц

Что это

Когда таблица уже создана, а нужно добавить колонку, индекс или изменить тип — новая миграция с Schema::table.

Создание

php artisan make:migration add_status_to_posts_table --table=posts

Флаг --table=posts создаёт заготовку.

Файл:

public function up(): void
{
    Schema::table('posts', function (Blueprint $table) {
        //
    });
}

public function down(): void
{
    Schema::table('posts', function (Blueprint $table) {
        //
    });
}

Добавление колонки

Schema::table('posts', function (Blueprint $table) {
    $table->string('status')->default('draft')->after('title');
});

down:

Schema::table('posts', function (Blueprint $table) {
    $table->dropColumn('status');
});

Несколько колонок сразу

Schema::table('posts', function (Blueprint $table) {
    $table->string('status')->default('draft');
    $table->boolean('featured')->default(false);
    $table->timestamp('published_at')->nullable();
});

Одна миграция — одна логическая группа. Например, все поля «статус» вместе.

Удаление колонки

$table->dropColumn('status');
$table->dropColumn(['status', 'featured', 'published_at']);

⚠️ Потеря данных — необратимо.

В down нужно вернуть колонку:

public function down(): void
{
    Schema::table('posts', function (Blueprint $table) {
        $table->string('status')->default('draft');
    });
}

⚠️ Данные не восстановятся. Только структура.

Переименование колонки

$table->renameColumn('title', 'name');

⚠️ Требует doctrine/dbal в старых MySQL:

composer require doctrine/dbal

Laravel 11+ — не требует.

⚠️ Потеря данных, если длины не совпадают.

Изменение типа — change()

Требует полного переопределения:

$table->string('title', 500)->change();
$table->integer('views')->nullable()->change();
$table->text('bio')->change();

Полный пример:

Schema::table('posts', function (Blueprint $table) {
    $table->string('title', 500)->change();
    $table->string('slug')->unique()->change();
});

Что задаёшь — то и станет. Всё, что не указал, сбросится.

⚠️ Порядок: для change нужен doctrine/dbal (старые MySQL) или свежая версия Laravel.

Индексы

Добавить:

$table->index('status');
$table->unique('slug');
$table->fullText('body');
$table->index(['status', 'published_at']);

Удалить:

$table->dropIndex(['status']);            // по колонкам
$table->dropUnique(['slug']);
$table->dropFullText('body');
$table->dropIndex('posts_status_idx');    // по имени

Внешние ключи

Добавить:

$table->foreignId('user_id')
    ->after('id')
    ->constrained()
    ->cascadeOnDelete();

Удалить:

$table->dropForeign(['user_id']);
$table->dropConstrainedForeignId('user_id');   // + колонка

Переименование таблицы

Schema::rename('posts', 'articles');

⚠️ Ломает код — модели, роуты, FK.

Безопасное удаление

Если колонки может не быть:

if (Schema::hasColumn('posts', 'status')) {
    Schema::table('posts', function (Blueprint $table) {
        $table->dropColumn('status');
    });
}

Полезно в миграциях между средами.

Порядок операций

В up:

  1. Добавить колонку.
  2. Заполнить данными (если нужно).
  3. Добавить индекс / FK.

В down:

  1. Удалить индекс / FK.
  2. Удалить колонку.

⚠️ Не оставляй orphan-индексы.

Изменение существующих данных

В миграции можно обновлять данные:

public function up(): void
{
    Schema::table('posts', function (Blueprint $table) {
        $table->string('status')->default('draft');
    });

    DB::table('posts')->whereNull('status')->update(['status' => 'draft']);
}

⚠️ Осторожно на больших таблицах — может блокировать.

Полный пример

public function up(): void
{
    Schema::table('posts', function (Blueprint $table) {
        $table->string('status')
            ->default('draft')
            ->after('title')
            ->index();

        $table->boolean('featured')
            ->default(false)
            ->after('status');

        $table->timestamp('published_at')
            ->nullable()
            ->after('featured');

        $table->index(['status', 'published_at']);
    });
}

public function down(): void
{
    Schema::table('posts', function (Blueprint $table) {
        $table->dropIndex(['status', 'published_at']);
        $table->dropColumn(['status', 'featured', 'published_at']);
    });
}

Практика

1. Добавь в posts:

  • status — enum(draft/published), default draft.
  • published_at — timestamp, nullable.
  • views_count — integer, default 0.

2. Добавь индекс:

  • Составной по status, published_at.

3. Напиши down — точно наоборот.

4. Проверь:

php artisan migrate
php artisan db:show --table=posts
php artisan migrate:rollback

Частые ошибки

1. Schema::create вместо Schema::table:

Schema::create('posts', ...);   // ОШИБКА: таблица уже есть

// Правильно:
Schema::table('posts', ...)

2. Забыли down:

public function down(): void { }
// migrate:rollback не откатит

3. dropColumn без down для добавления:

up():    добавь колонку
// down: пусто → колонка останется

4. change без полного описания:

$table->string('title')->change();   // длина 255, not null
// Потеряли nullable и default

5. renameColumn без doctrine/dbal (старые MySQL):

composer require doctrine/dbal

Итог

  • Schema::table — для изменения.
  • dropColumn — удалить.
  • renameColumn — переименовать.
  • ->change() — изменить тип.
  • dropIndex, dropForeign — удалить индексы.
  • down — точное отражение up.
  • Правило: данные теряются, но структура — восстанавливается.

Дальше — индексы и производительность.