Laravel
Database & Migrations
Complete reference for creating and running migrations, all common column types, foreign keys, indexes, rollback commands, database seeders, and model factories with Faker.
Artisan Commands: Use
php artisan make:migration to create migration files. The --create flag pre-populates a Schema::create() call, while --table generates an Schema::table() for modifying existing tables.
terminal – migration commands
BASH
# Create a new migration (auto-generates table name from migration name)
php artisan make:migration create_posts_table --create=posts
php artisan make:migration create_categories_table --create=categories
# Modify an existing table
php artisan make:migration add_status_to_posts_table --table=posts
php artisan make:migration drop_legacy_column_from_users_table --table=users
# Run all pending migrations
php artisan migrate
# Run with verbose output
php artisan migrate -v
# Migrate a specific path
php artisan migrate --path=/database/migrations/2024_01_01_create_posts_table.php
# Roll back the last batch of migrations
php artisan migrate:rollback
# Roll back a specific number of steps
php artisan migrate:rollback --step=3
# Roll back ALL migrations
php artisan migrate:reset
# Roll back and re-run all migrations (destructive!)
php artisan migrate:refresh
# Drop all tables and re-migrate (destructive!)
php artisan migrate:fresh
# Seed after migrating
php artisan migrate:fresh --seed
# Show migration status
php artisan migrate:status
# Squash all migrations into a single SQL dump
php artisan schema:dump
Migration Structure: A migration file has
up() for applying changes and down() for reverting them. Always implement down() so rollbacks work correctly.
database/migrations/2024_01_01_000000_create_posts_table.php
PHP
<?php
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('posts', function (Blueprint $table) {
// ── Primary Key ───────────────────────────────────
$table->id(); // BIGINT UNSIGNED AUTO_INCREMENT PK
// $table->uuid('id')->primary(); // UUID primary key
// $table->ulid('id')->primary(); // ULID primary key
// ── String / Text ─────────────────────────────────
$table->string('title'); // VARCHAR(255)
$table->string('slug', 191)->unique(); // VARCHAR(191) UNIQUE
$table->string('excerpt')->nullable(); // nullable VARCHAR
$table->text('body'); // TEXT
$table->mediumText('summary'); // MEDIUMTEXT
$table->longText('content'); // LONGTEXT
$table->char('code', 6); // CHAR(6)
// ── Numbers ───────────────────────────────────────
$table->tinyInteger('sort_order')->default(0);
$table->smallInteger('view_count')->unsigned()->default(0);
$table->integer('likes')->default(0);
$table->bigInteger('impressions')->default(0);
$table->unsignedBigInteger('category_id');
$table->float('rating', 3, 1)->default(0.0); // FLOAT(3,1)
$table->double('score', 5, 2)->nullable();
$table->decimal('price', 10, 2)->default(0.00);
// ── Boolean ───────────────────────────────────────
$table->boolean('is_featured')->default(false);
$table->boolean('is_published')->default(false);
// ── Enums & Sets ──────────────────────────────────
$table->enum('status', ['draft', 'published', 'archived'])->default('draft');
$table->set('tags', ['laravel', 'php', 'vue', 'react']);
// ── Dates & Times ─────────────────────────────────
$table->date('published_on')->nullable();
$table->time('publish_time')->nullable();
$table->dateTime('scheduled_at')->nullable();
$table->timestamp('published_at')->nullable();
$table->year('release_year')->nullable();
// ── JSON ──────────────────────────────────────────
$table->json('meta')->nullable();
$table->jsonb('settings')->nullable(); // PostgreSQL only
// ── Binary / UUID ─────────────────────────────────
$table->uuid('external_id')->nullable();
$table->binary('thumbnail')->nullable();
// ── IP / MAC ──────────────────────────────────────
$table->ipAddress('author_ip')->nullable();
$table->macAddress('device_mac')->nullable();
// ── Foreign Keys ──────────────────────────────────
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
// same as:
// $table->unsignedBigInteger('user_id');
// $table->foreign('user_id')->references('id')->on('users')->cascadeOnDelete();
$table->foreignId('category_id')->nullable()->constrained()->nullOnDelete();
// Foreign to a differently-named table
$table->foreignId('author_id')->constrained('users')->cascadeOnDelete();
// ── Soft Deletes & Timestamps ─────────────────────
$table->softDeletes(); // adds deleted_at TIMESTAMP NULL
$table->timestamps(); // adds created_at & updated_at
// ── Indexes ───────────────────────────────────────
$table->index('status');
$table->index(['status', 'published_at'], 'idx_status_published');
$table->fullText(['title', 'body'], 'ft_title_body'); // MySQL only
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('posts');
}
};
Modifying Existing Tables: Use
Schema::table() to add, change, or drop columns on an existing table. The ->change() method requires the doctrine/dbal package in Laravel 9 and below (built-in from Laravel 10+).
database/migrations/2024_06_01_000000_add_status_to_posts_table.php
PHP
<?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('posts', function (Blueprint $table) {
// Add new columns
$table->string('cover_image')->nullable()->after('slug');
$table->unsignedInteger('read_time')->default(0)->after('body');
// Rename a column
$table->renameColumn('body', 'content');
// Modify an existing column (no doctrine/dbal needed in L10+)
$table->string('title', 500)->change();
$table->text('excerpt')->nullable()->change();
// Drop a column
$table->dropColumn('legacy_field');
// Drop multiple columns
$table->dropColumn(['old_col_one', 'old_col_two']);
// Drop a foreign key and its column
$table->dropConstrainedForeignId('category_id');
// Add an index after table creation
$table->index('cover_image');
$table->dropIndex('posts_status_index');
});
}
public function down(): void
{
Schema::table('posts', function (Blueprint $table) {
$table->dropColumn(['cover_image', 'read_time']);
$table->renameColumn('content', 'body');
$table->string('title', 255)->change();
});
}
};
Seeders: Seeders populate the database with test or initial data. The
DatabaseSeeder is the entry point — it calls individual seeders via call(). Run with php artisan db:seed or php artisan migrate:fresh --seed.
database/seeders/DatabaseSeeder.php
PHP
<?php
namespace Database\Seeders;
use App\Models\User;
use App\Models\Post;
use App\Models\Category;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\Hash;
class DatabaseSeeder extends Seeder
{
public function run(): void
{
// Run specific seeders in order
$this->call([
CategorySeeder::class,
UserSeeder::class,
PostSeeder::class,
]);
}
}
// ─────────────────────────────────────────────
// database/seeders/UserSeeder.php
// ─────────────────────────────────────────────
class UserSeeder extends Seeder
{
public function run(): void
{
// Create a fixed admin user
User::firstOrCreate(
['email' => 'admin@example.com'],
[
'name' => 'Admin User',
'password' => Hash::make('password'),
'role' => 'admin',
'email_verified_at' => now(),
]
);
// Generate 20 random users via factory
User::factory(20)->create();
// Generate 5 editors using a named state
User::factory(5)->editor()->create();
}
}
// ─────────────────────────────────────────────
// database/seeders/PostSeeder.php
// ─────────────────────────────────────────────
class PostSeeder extends Seeder
{
public function run(): void
{
// Assign posts to existing users
User::all()->each(function (User $user) {
Post::factory(rand(2, 8))
->for($user)
->create();
});
// Create published posts with a specific state
Post::factory(10)->published()->create();
}
}
// Artisan commands:
// php artisan make:seeder UserSeeder
// php artisan db:seed -- run DatabaseSeeder
// php artisan db:seed --class=UserSeeder -- run specific seeder
// php artisan migrate:fresh --seed -- fresh + seed
Model Factories: Factories use Faker to generate realistic fake data for testing and seeding. Define states to represent different model configurations. Generate with
php artisan make:factory PostFactory --model=Post.
database/factories/PostFactory.php
PHP
<?php
namespace Database\Factories;
use App\Models\User;
use App\Models\Category;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Str;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Post>
*/
class PostFactory extends Factory
{
/**
* Define the model's default state.
*/
public function definition(): array
{
$title = fake()->sentence(rand(5, 10));
return [
'user_id' => User::factory(), // creates a related user
'category_id' => Category::factory(),
'title' => $title,
'slug' => Str::slug($title),
'excerpt' => fake()->paragraph(2),
'body' => fake()->paragraphs(5, true),
'status' => fake()->randomElement(['draft', 'published']),
'is_featured' => fake()->boolean(20), // 20% chance true
'rating' => fake()->randomFloat(1, 1, 5),
'published_at' => null,
'meta' => [
'seo_title' => $title,
'seo_description' => fake()->sentence(15),
'og_image' => fake()->imageUrl(1200, 630),
],
];
}
/**
* State: Published post with a published_at timestamp.
*/
public function published(): static
{
return $this->state(fn (array $attributes) => [
'status' => 'published',
'published_at' => fake()->dateTimeBetween('-6 months', 'now'),
]);
}
/**
* State: Archived post.
*/
public function archived(): static
{
return $this->state(fn (array $attributes) => [
'status' => 'archived',
'published_at' => fake()->dateTimeBetween('-1 year', '-6 months'),
]);
}
/**
* State: Featured post.
*/
public function featured(): static
{
return $this->state(fn (array $attributes) => [
'is_featured' => true,
]);
}
}
// ─────────────────────────────────────────────
// UserFactory – with editor state
// ─────────────────────────────────────────────
// class UserFactory extends Factory {
// public function definition(): array {
// return [
// 'name' => fake()->name(),
// 'email' => fake()->unique()->safeEmail(),
// 'password' => Hash::make('password'),
// 'role' => 'user',
// 'email_verified_at' => now(),
// 'remember_token' => Str::random(10),
// ];
// }
// public function editor(): static {
// return $this->state(fn () => ['role' => 'editor']);
// }
// }
// ─────────────────────────────────────────────
// Using factories in tests / tinker
// ─────────────────────────────────────────────
// Post::factory()->create(); -- persists one
// Post::factory(5)->create(); -- persists five
// Post::factory()->make(); -- in-memory only
// Post::factory()->published()->featured()->create();
// Post::factory()->for(User::find(1))->create(); -- attach to existing user
// Post::factory()->has(Comment::factory(3))->create(); -- with hasMany