Миграции -- это система версионирования схемы базы данных. Каждая миграция -- это класс с методами up() (применить) и down() (откатить). Миграции выполняются в хронологическом порядке и позволяют всей команде работать с одинаковой структурой БД.
Создание миграций
# Create a migration
php artisan make:migration create_flights_table
# Create a table migration (generates up/down boilerplate)
php artisan make:migration create_flights_table --create=flights
# Create an alter table migration
php artisan make:migration add_destination_to_flights_table --table=flights
# Create a model with migration
php artisan make:model Flight -m
<?php
declare(strict_types=1);
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('flights', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('airline');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('flights');
}
};
Типы колонок
Числовые типы
Schema::create('products', function (Blueprint $table) {
$table->id(); // BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY
$table->bigIncrements('id'); // Same as id()
$table->bigInteger('votes'); // BIGINT
$table->decimal('amount', 8, 2); // DECIMAL(8,2)
$table->double('amount'); // DOUBLE
$table->float('amount', precision: 53); // FLOAT
$table->increments('id'); // INT UNSIGNED AUTO_INCREMENT
$table->integer('votes'); // INT
$table->mediumIncrements('id'); // MEDIUMINT UNSIGNED AUTO_INCREMENT
$table->mediumInteger('votes'); // MEDIUMINT
$table->smallIncrements('id'); // SMALLINT UNSIGNED AUTO_INCREMENT
$table->smallInteger('votes'); // SMALLINT
$table->tinyIncrements('id'); // TINYINT UNSIGNED AUTO_INCREMENT
$table->tinyInteger('votes'); // TINYINT
$table->unsignedBigInteger('votes'); // UNSIGNED BIGINT
$table->unsignedInteger('votes'); // UNSIGNED INT
$table->unsignedMediumInteger('votes');
$table->unsignedSmallInteger('votes');
$table->unsignedTinyInteger('votes');
});
Строковые и текстовые типы
Schema::create('posts', function (Blueprint $table) {
$table->char('code', 4); // CHAR(4)
$table->string('name', 255); // VARCHAR(255)
$table->tinyText('notes'); // TINYTEXT
$table->text('description'); // TEXT
$table->mediumText('content'); // MEDIUMTEXT
$table->longText('body'); // LONGTEXT
});
Дата и время
Schema::create('events', function (Blueprint $table) {
$table->date('birthday'); // DATE
$table->dateTime('starts_at', precision: 0); // DATETIME
$table->dateTimeTz('starts_at'); // DATETIME with timezone
$table->time('sunrise', precision: 0); // TIME
$table->timeTz('sunrise'); // TIME with timezone
$table->timestamp('added_at', precision: 0); // TIMESTAMP
$table->timestampTz('added_at'); // TIMESTAMP with timezone
$table->timestamps(precision: 0); // created_at, updated_at TIMESTAMP
$table->timestampsTz(precision: 0); // With timezone
$table->softDeletes(precision: 0); // deleted_at TIMESTAMP nullable
$table->softDeletesTz(precision: 0); // With timezone
$table->year('birth_year'); // YEAR
});
Специальные типы
Schema::create('misc', function (Blueprint $table) {
$table->binary('data'); // BLOB
$table->boolean('confirmed'); // BOOLEAN (TINYINT(1))
$table->enum('difficulty', ['easy', 'hard']); // ENUM
$table->set('flavors', ['strawberry', 'vanilla']); // SET
$table->geometry('positions'); // GEOMETRY
$table->ipAddress('visitor'); // VARCHAR (for IP)
$table->json('options'); // JSON
$table->jsonb('options'); // JSONB (PostgreSQL)
$table->macAddress('device'); // VARCHAR (for MAC)
$table->morphs('taggable'); // taggable_type VARCHAR, taggable_id BIGINT UNSIGNED
$table->nullableMorphs('taggable'); // Same, but nullable
$table->ulidMorphs('taggable'); // taggable_type VARCHAR, taggable_id CHAR(26)
$table->uuidMorphs('taggable'); // taggable_type VARCHAR, taggable_id CHAR(36)
$table->rememberToken(); // remember_token VARCHAR(100) nullable
$table->ulid('id'); // CHAR(26)
$table->uuid('id'); // CHAR(36) or UUID type
$table->foreignId('user_id'); // BIGINT UNSIGNED (for FK)
$table->foreignIdFor(User::class); // Same, inferred name
$table->foreignUlid('user_id'); // CHAR(26) (for FK)
$table->foreignUuid('user_id'); // CHAR(36) (for FK)
});
Модификаторы колонок
Schema::create('users', function (Blueprint $table) {
$table->id();
// Nullable
$table->string('bio')->nullable();
// Default value
$table->integer('votes')->default(0);
$table->boolean('active')->default(true);
$table->json('settings')->default('{}');
$table->timestamp('published_at')->nullable()->default(null);
// Auto-increment starting value
$table->id()->from(1000);
// Comment
$table->string('email')->comment('User email address');
// After specific column (MySQL only)
$table->string('phone')->after('email');
// First column (MySQL only)
$table->string('ssn')->first();
// Invisible (MySQL only)
$table->string('internal_code')->invisible();
// Unsigned
$table->integer('quantity')->unsigned();
// Using expression as default
$table->timestamp('created_at')->useCurrent();
$table->timestamp('updated_at')->useCurrentOnUpdate();
// Virtual/Stored generated columns
$table->string('full_name')
->virtualAs("CONCAT(first_name, ' ', last_name)");
$table->string('full_name')
->storedAs("CONCAT(first_name, ' ', last_name)");
// Charset and collation (MySQL)
$table->string('name')->charset('utf8mb4')->collation('utf8mb4_unicode_ci');
});
Индексы
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('email');
$table->string('name');
$table->string('phone')->nullable();
$table->text('bio')->nullable();
// Unique index
$table->unique('email');
// Regular index
$table->index('name');
// Composite index
$table->index(['name', 'email']);
// Named index
$table->index('name', 'idx_users_name');
// Full text index (MySQL/PostgreSQL)
$table->fullText('bio');
// Spatial index
// $table->spatialIndex('location');
// Inline index definition
$table->string('email')->unique();
$table->string('name')->index();
});
Удаление индексов
Schema::table('users', function (Blueprint $table) {
// Drop by index name
$table->dropIndex('idx_users_name');
// Drop by column(s) - Laravel auto-generates the name
$table->dropIndex(['name']); // Drops: users_name_index
$table->dropUnique(['email']); // Drops: users_email_unique
$table->dropFullText(['bio']); // Drops: users_bio_fulltext
// Drop primary key
$table->dropPrimary('users_id_primary');
});
Внешние ключи (Foreign Keys)
Schema::create('posts', function (Blueprint $table) {
$table->id();
$table->string('title');
// Method 1: Explicit foreign key
$table->unsignedBigInteger('user_id');
$table->foreign('user_id')
->references('id')
->on('users')
->onDelete('cascade')
->onUpdate('cascade');
// Method 2: Shorthand with foreignId (recommended)
$table->foreignId('user_id')->constrained();
// Automatically: REFERENCES users(id)
// Method 3: With options
$table->foreignId('user_id')
->constrained()
->cascadeOnDelete()
->cascadeOnUpdate();
// Method 4: Custom table/column
$table->foreignId('author_id')
->constrained(table: 'users', column: 'id');
// Nullable foreign key
$table->foreignId('category_id')
->nullable()
->constrained()
->nullOnDelete();
// Foreign key actions
$table->foreignId('team_id')
->constrained()
->cascadeOnDelete() // ON DELETE CASCADE
->cascadeOnUpdate(); // ON UPDATE CASCADE
$table->foreignId('team_id')
->constrained()
->restrictOnDelete() // ON DELETE RESTRICT
->restrictOnUpdate(); // ON UPDATE RESTRICT
$table->foreignId('team_id')
->constrained()
->nullOnDelete(); // ON DELETE SET NULL
$table->foreignId('team_id')
->constrained()
->noActionOnDelete(); // ON DELETE NO ACTION
// UUID foreign key
$table->foreignUuid('team_id')->constrained();
// ULID foreign key
$table->foreignUlid('team_id')->constrained();
$table->timestamps();
});
Удаление внешних ключей
Schema::table('posts', function (Blueprint $table) {
// Drop by convention name
$table->dropForeign(['user_id']); // Drops: posts_user_id_foreign
// Drop by explicit name
$table->dropForeign('posts_user_id_foreign');
// Disable/enable foreign key constraints
Schema::disableForeignKeyConstraints();
Schema::enableForeignKeyConstraints();
// Or using withoutForeignKeyConstraints
Schema::withoutForeignKeyConstraints(function () {
// Perform operations that might violate FK constraints
});
});
Изменение колонок
// Rename a column
Schema::table('users', function (Blueprint $table) {
$table->renameColumn('from', 'to');
});
// Change column type/attributes
Schema::table('users', function (Blueprint $table) {
$table->string('name', 50)->change(); // Change length
$table->integer('votes')->unsigned()->default(1)->change(); // Change type & default
$table->string('bio')->nullable()->change(); // Make nullable
});
// Drop columns
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('votes');
// Drop multiple columns
$table->dropColumn(['votes', 'avatar', 'location']);
});
// Rename table
Schema::rename('users', 'members');
// Drop table
Schema::drop('users');
Schema::dropIfExists('users');
Запуск миграций
# Run all outstanding migrations
php artisan migrate
# Run with seed
php artisan migrate --seed
# Rollback the last batch
php artisan migrate:rollback
# Rollback last N batches
php artisan migrate:rollback --step=3
# Rollback all migrations
php artisan migrate:reset
# Rollback and re-run all migrations
php artisan migrate:refresh
php artisan migrate:refresh --seed
# Drop all tables and re-run all migrations
php artisan migrate:fresh
php artisan migrate:fresh --seed
# Migration status
php artisan migrate:status
# Pretend (show SQL without running)
php artisan migrate --pretend
# Force in production
php artisan migrate --force
# Specific database connection
php artisan migrate --database=mysql
# Specific path
php artisan migrate --path=/database/migrations/2024_01_01
Squashing Migrations
Когда миграций становится слишком много, их можно "сжать" в один SQL-файл.
# Create a squash file (database/schema/mysql-schema.sql)
php artisan schema:dump
# Dump and prune (remove old migration files)
php artisan schema:dump --prune
При следующем migrate:fresh Laravel сначала выполнит schema dump, затем оставшиеся миграции.
Практический пример: полная миграция
<?php
declare(strict_types=1);
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::create('orders', function (Blueprint $table) {
// Primary key
$table->id();
// Foreign keys
$table->foreignId('user_id')
->constrained()
->cascadeOnDelete();
$table->foreignId('shipping_address_id')
->nullable()
->constrained('addresses')
->nullOnDelete();
// Order data
$table->string('order_number', 32)->unique();
$table->enum('status', ['pending', 'processing', 'shipped', 'delivered', 'cancelled'])
->default('pending');
$table->decimal('subtotal', 10, 2);
$table->decimal('tax', 10, 2)->default(0);
$table->decimal('shipping_cost', 10, 2)->default(0);
$table->decimal('total', 10, 2);
$table->string('currency', 3)->default('USD');
// Payment
$table->string('payment_method')->nullable();
$table->string('payment_id')->nullable();
$table->timestamp('paid_at')->nullable();
// Notes
$table->text('notes')->nullable();
$table->json('metadata')->nullable();
// Timestamps and soft deletes
$table->timestamps();
$table->softDeletes();
// Indexes
$table->index('status');
$table->index('created_at');
$table->index(['user_id', 'status']);
});
}
public function down(): void
{
Schema::dropIfExists('orders');
}
};
Проверка существования
// Check if table exists
if (Schema::hasTable('users')) {
// ...
}
// Check if column exists
if (Schema::hasColumn('users', 'email')) {
// ...
}
// Check multiple columns
if (Schema::hasColumns('users', ['email', 'name'])) {
// ...
}
// Get column type
$type = Schema::getColumnType('users', 'email'); // 'string'
// Get all column names
$columns = Schema::getColumnListing('users');
// Get all table names
$tables = Schema::getTables();
// Get all indexes
$indexes = Schema::getIndexes('users');
// Get foreign keys
$foreignKeys = Schema::getForeignKeys('orders');