Как я проектирую миграции и базу в Laravel, чтобы потом не переписывать всё
В Laravel очень легко начать «на коленке»: накидать пару таблиц, связи додумать потом, типы полей взять «на глаз». Через год такой проект обычно просит «срочно всё переделать». Я стараюсь заложить нормальную схему ещё в момент первых миграций.
С чего начинаю проектирование
Перед первой миграцией отвечаю себе на три вопроса:
- какие основные сущности есть в системе (User, Product, Order и т.д.)
- какие между ними связи (один-ко-многим, многие-ко-многим)
- какие сценарии будут самыми частыми (по чему ищем, фильтруем, сортируем)
Из этих ответов рождаются первые таблицы и индексы, а не наоборот.
Базовый шаблон миграции
Я держусь простого и понятного набора полей:
Schema::create('products', function (Blueprint $table) { $table->id(); $table->string('name'); $table->string('sku')->unique(); $table->decimal('price', 10, 2)->index(); $table->unsignedBigInteger('brand_id')->index(); $table->timestamps(); });
Правила:
- числовые поля для денег только decimal, никакого float
- всё, по чему будет поиск/фильтр, сразу получает index()
- timestamps() ставлю почти везде
Связи: foreign keys или нет Если проект под контролем и миграции проходят через деплой:
- ставлю foreignId()->constrained() везде, где это логично
- это помогает поймать мусорные данные ещё на уровне БД
Если база огромная и обновления сложные, могу:
- оставить unsignedBigInteger + индексы
- контролировать целостность в приложении
Главное — не мешать подходы и не делать часть таблиц «строгими», а часть «как получится».
Многие-ко-многим и pivot-таблицы Типичный пример: товары и категории.
Schema::create('category_product', function (Blueprint $table) { $table->unsignedBigInteger('category_id'); $table->unsignedBigInteger('product_id');
$table->primary(['category_id', 'product_id']); });
Сразу задаю составной primary key, чтобы не плодить дубликаты. Если у связи есть свои поля (например, quantity или sort), добавляю их сюда же.
Эволюция схемы: отдельные миграции для изменений Самая частая ошибка — править существующие миграции. Я так не делаю:
- любая правка структуры — новая миграция add_field_to_table, rename_column
- старые миграции остаются как исторический слепок
Так можно безопасно откатывать и раскатывать изменения на разных окружениях.
Индексы: что я добавляю сразу Я всегда думаю глазами запросов. Индексы ставлю:
- на поля, участвующие в WHERE и ORDER BY
- на внешние ключи
- на сочетания полей, по которым фильтруем вместе
Пример:
$table->index(['status', 'created_at']);
Потом это экономит часы поиска «почему запрос на список заказов так медленный».
Мин**и-чек-лист перед тем, как нажать migrate
*** Понимаю сущности и связи в домене * Денежные поля сделаны через decimal с нужной точностью * Часто используемые поля и связи проиндексированы * Для многих-ко-многим есть pivot-таблицы без дубликатов * Любые изменения схемы идут отдельными миграциями, не правлю старые * Есть план, как откатывать миграции на тесте и проде
Итог Хорошие миграции в Laravel — это не про красивые классы, а про дисциплину: сначала продумать сущности и связи, сразу заложить индексы и аккуратно эволюционировать схему через новые миграции. Тогда даже через пару лет к базе можно спокойно прикасаться, а не бояться, что любое изменение уронит половину проекта.