Laravel миграции и сиды: таблица уже существует, ошибка SQLSTATE, foreign key и другие проблемы

mr. Cooper 1 час назад Веб-разработка
Laravel миграции и сиды: таблица уже существует, ошибка SQLSTATE, foreign key и другие проблемы

Если вы работаете с Laravel и столкнулись с ошибкой при запуске php artisan migrate - вы не одиноки. Проблемы с миграциями, сидами, фабриками и Redis возникают у большинства разработчиков. В этой статье разберём пять самых частых ситуаций: от «таблица уже существует» до «Redis не подключается». Каждый случай - с конкретным решением и примерами кода.

Статья поможет новичкам и разработчикам среднего уровня, которые хотят быстро устранить проблему, а не часами читать документацию.

Что такое миграции, сиды и фабрики в Laravel

Миграции - это версионный контроль для базы данных. Вместо того чтобы вручную создавать таблицы через SQL, вы описываете структуру в PHP-файлах, а Laravel сам выполняет нужные команды.

Сиды (Seeders) - наполняют базу начальными данными. Например, список категорий, тестовые пользователи, начальные настройки.

Фабрики (Factories) - генерируют фейковые данные для тестов. Связаны с Faker и позволяют создавать модели одной строкой: User::factory(50)->create().

Redis - хранилище ключ-значение, которое Laravel использует для кешей, очередей и сессий.

Почему возникают ошибки

Большинство проблем с миграциями появляется из-за:

  • ручного вмешательства в базу данных в обход миграций;

  • нарушения порядка выполнения - таблица с внешним ключом создаётся раньше родительской;

  • некорректного состояния таблицы migrations в БД;

  • неправильной конфигурации .env;

  • конфликтов при работе в команде (один разработчик изменил структуру, другой - нет).

Частые ошибки и как их исправить

1. Laravel migration: таблица уже существует - SQLSTATE[42S01]

Ошибка:

SQLSTATE[42S01]: Base table or view already exists: 1050 Table 'users' already exists

Это значит, что таблица уже есть в базе, но Laravel пытается создать её заново. Обычно так бывает, если таблицу создали вручную или миграция была выполнена частично.

Решение 1 - проверить таблицу migrations:

php artisan migrate:status

Если в списке миграция есть, но таблица уже существует - скорее всего, кто-то создал её в обход Laravel.

Решение 2 - добавить проверку в миграцию:

// database/migrations/xxxx_create_users_table.php

public function up(): void
{
    if (!Schema::hasTable('users')) {
        Schema::create('users', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->string('email')->unique();
            $table->timestamps();
        });
    }
}

Решение 3 - сброс и повтор (только для dev-среды!):

php artisan migrate:fresh
# или
php artisan migrate:rollback
php artisan migrate

migrate:fresh удаляет все таблицы. Никогда не используйте на продакшене.

Решение 4 - вручную добавить запись в migrations:

Если таблица уже существует и данные нужно сохранить:

INSERT INTO migrations (migration, batch) VALUES ('2024_01_01_000000_create_users_table', 1);

2. Laravel Seeder: данные не вставляются, ошибка foreign key

Ошибка:

SQLSTATE[23000]: Integrity constraint violation: 1452 Cannot add or update a child row: a foreign key constraint fails

Сидер пытается вставить запись с user_id, но пользователь с таким ID ещё не создан - нарушена очерёдность.

Решение 1 - отключить проверку внешних ключей временно:

// database/seeders/DatabaseSeeder.php

public function run(): void
{
    DB::statement('SET FOREIGN_KEY_CHECKS=0;');

    $this->call([
        UserSeeder::class,
        PostSeeder::class,
    ]);

    DB::statement('SET FOREIGN_KEY_CHECKS=1;');
}

Решение 2 - соблюдать порядок вызова сидеров:

public function run(): void
{
    // Сначала - родительские таблицы
    $this->call(UserSeeder::class);
    // Потом - зависимые
    $this->call(PostSeeder::class);
}

Решение 3 - использовать фабрику с привязкой:

public function run(): void
{
    $users = User::all();

    Post::factory(20)->create([
        'user_id' => fn() => $users->random()->id,
    ]);
}

3. Laravel Factory: создаёт неправильные данные, нарушена логика

Фабрика генерирует email без @, дата рождения раньше даты регистрации, статус не соответствует типу.

Пример проблемной фабрики:

// ПЛОХО - нет логической связи между полями
public function definition(): array
{
    return [
        'status' => $this->faker->randomElement(['active', 'banned']),
        'banned_at' => null, // всегда null, даже при status=banned
    ];
}

Правильный вариант - с состояниями (states):

// database/factories/UserFactory.php

public function definition(): array
{
    return [
        'name'      => fake()->name(),
        'email'     => fake()->unique()->safeEmail(),
        'status'    => 'active',
        'banned_at' => null,
    ];
}

// Состояние "забанен"
public function banned(): static
{
    return $this->state(fn(array $attributes) => [
        'status'    => 'banned',
        'banned_at' => now()->subDays(rand(1, 30)),
    ]);
}

Использование:

User::factory()->create();              // обычный активный пользователь
User::factory()->banned()->create();    // забаненный с датой
User::factory(10)->banned()->create();  // 10 забаненных

Последовательные данные вместо случайных:

'order_number' => fake()->unique()->numberBetween(1000, 9999),
'created_at'   => fake()->dateTimeBetween('-1 year', 'now'),

4. Laravel Redis: не подключается, ошибка Connection refused

Ошибка:

Connection refused [tcp://127.0.0.1:6379]

Шаг 1 - проверить, запущен ли Redis:

redis-cli ping
# Ответ: PONG - всё ок
# Нет ответа - Redis не запущен

Запустить Redis:

# Linux/macOS
sudo service redis start
# или
redis-server

# macOS через Homebrew
brew services start redis

Шаг 2 - проверить .env:

REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379

Шаг 3 - проверить config/database.php:

'redis' => [
    'client' => env('REDIS_CLIENT', 'phpredis'), // или 'predis'
    'default' => [
        'host'     => env('REDIS_HOST', '127.0.0.1'),
        'password' => env('REDIS_PASSWORD', null),
        'port'     => env('REDIS_PORT', 6379),
        'database' => env('REDIS_DB', 0),
    ],
],

Шаг 4 - установить нужный клиент:

# Если используете phpredis
sudo apt install php-redis   # Linux
pecl install redis           # macOS

# Если используете predis
composer require predis/predis

И в .env:

REDIS_CLIENT=predis

Шаг 5 - в Docker-среде:

# docker-compose.yml
services:
  redis:
    image: redis:alpine
    ports:
      - "6379:6379"

В .env замените 127.0.0.1 на имя сервиса:

REDIS_HOST=redis

Примеры: типичные сценарии и решения

Сценарий: миграция упала на полпути, часть таблиц создана

# Откатить последний батч
php artisan migrate:rollback --step=1

# Посмотреть статус
php artisan migrate:status

# Запустить снова
php artisan migrate

Сценарий: нужно заново заполнить базу без потери структуры

php artisan migrate:fresh --seed

Сценарий: запустить только один сидер

php artisan db:seed --class=UserSeeder

Часто задаваемые вопросы (FAQ)

Как откатить одну конкретную миграцию? Laravel откатывает миграции батчами. Чтобы откатить конкретную - используйте migrate:rollback --step=N, где N - количество батчей назад. Конкретный файл откатить нельзя, только вручную через Schema::dropIfExists().

Можно ли изменить миграцию после её выполнения? Нельзя - если миграция уже выполнена, создайте новую. Иначе другие разработчики в команде получат рассинхрон состояния БД.

Почему php artisan migrate ничего не делает? Скорее всего, все миграции уже выполнены. Запустите php artisan migrate:status - если все строки помечены Ran, нечего выполнять.

Как добавить колонку в существующую таблицу? Создайте новую миграцию: php artisan make:migration add_phone_to_users_table --table=users. Внутри используйте $table->string('phone')->nullable()->after('email');.

Ошибка Class not found при запуске сидера - что делать? Выполните composer dump-autoload - классы сидеров могут не быть загружены автозагрузчиком после создания.

Как использовать фабрику с конкретными данными?

User::factory()->create(['email' => 'test@example.com', 'name' => 'Иван']);

Переданные значения перезапишут данные от Faker.

Redis работает, но кеш не сбрасывается - почему? Убедитесь, что CACHE_DRIVER=redis в .env, а не file. После изменения выполните php artisan config:cache.

Как запустить сиды вместе с миграциями?

php artisan migrate --seed
# или для полного сброса
php artisan migrate:fresh --seed

Полезные советы и лучшие практики

Называйте миграции описательно: create_orders_table, add_status_to_users_table, drop_legacy_tokens_table - через год вы скажете себе спасибо.

Всегда заполняйте метод down() в миграции. Миграции без отката - технический долг.

Используйте $table->softDeletes() вместо физического удаления, если данные могут понадобиться.

В сидерах используйте updateOrCreate вместо create, чтобы сид можно было запускать повторно:

User::updateOrCreate(
    ['email' => 'admin@example.com'],
    ['name' => 'Admin', 'password' => bcrypt('secret')]
);

Для больших объёмов данных в фабриках используйте chunk или lazy:

User::factory()->count(10000)->create(); // медленно
// Лучше разбить на части
foreach (range(1, 100) as $i) {
    User::factory()->count(100)->create();
}

Для Redis добавьте TTL к кешируемым данным, чтобы они не занимали память вечно:

Cache::put('key', $value, now()->addHours(24));

Итог

Проблемы с Laravel-миграциями почти всегда решаются проверкой состояния таблицы migrations, порядка выполнения сидеров и настроек .env. Ошибки foreign key - следствие нарушенного порядка. Неправильные данные фабрики лечатся состояниями (states). Redis чаще всего не подключается из-за неправильного хоста или незапущенного сервиса.

Запомните три команды, которые спасают в большинстве ситуаций:

php artisan migrate:status          # диагностика
php artisan migrate:fresh --seed    # сброс и повтор (только dev!)
composer dump-autoload              # обновление автозагрузчика

Комментарии

Пока нет комментариев. Будьте первым, кто напишет.

Чтобы оставить комментарий, войдите в аккаунт.

Похожие статьи