🏠 Home / Hub

Laravel 04 — Migration & Eloquent ORM

Migrations define your database schema in PHP code. Eloquent ORM lets you interact with your database using expressive, object-oriented syntax.

1. Database Configuration

Configure your database in .env. Laravel reads this into config/database.php.

# MySQL (most common)
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=blog_db
DB_USERNAME=root
DB_PASSWORD=

# SQLite (zero-config for prototyping)
DB_CONNECTION=sqlite
# DB_DATABASE=/absolute/path/to/database.sqlite
# Or just create: touch database/database.sqlite

# PostgreSQL
DB_CONNECTION=pgsql
DB_HOST=127.0.0.1
DB_PORT=5432
DB_DATABASE=blog_db
DB_USERNAME=postgres
DB_PASSWORD=secret
For local dev with XAMPP, leave DB_PASSWORD empty — XAMPP root has no password by default.

2. Creating Migrations

# Create migration for a new table
php artisan make:migration create_posts_table --create=posts

# Create migration to modify existing table
php artisan make:migration add_excerpt_to_posts_table --table=posts

# Create model AND migration together (recommended)
php artisan make:model Post -m
# Creates: app/Models/Post.php
#          database/migrations/xxxx_create_posts_table.php

Generated migration file (database/migrations/2024_01_01_000000_create_posts_table.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::create('posts', function (Blueprint $table) {
            $table->id();                          // BIGINT UNSIGNED AUTO_INCREMENT PK
            $table->foreignId('user_id')
                  ->constrained()
                  ->cascadeOnDelete();            // FK to users.id
            $table->string('title');               // VARCHAR(255)
            $table->string('slug')->unique();      // unique slug
            $table->text('body');                  // TEXT
            $table->string('image')->nullable();   // optional image path
            $table->boolean('published')->default(false);
            $table->timestamp('published_at')->nullable();
            $table->timestamps();                  // created_at + updated_at
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('posts');
    }
};

3. Schema Column Types

MethodSQL TypeNotes
$table->id()BIGINT UNSIGNED PKAuto-increment primary key
$table->string('col')VARCHAR(255)Add length: string('col', 100)
$table->text('col')TEXTFor longer text
$table->longText('col')LONGTEXTVery long content
$table->integer('col')INTAlso: tinyInteger, bigInteger
$table->float('col')FLOATDecimal: decimal('price', 8, 2)
$table->boolean('col')TINYINT(1)true/false
$table->date('col')DATEDate only
$table->timestamp('col')TIMESTAMPDate and time
$table->timestamps()2x TIMESTAMPcreated_at + updated_at
$table->softDeletes()TIMESTAMP NULLdeleted_at for soft delete
$table->foreignId('user_id')BIGINT UNSIGNEDForeign key column
->constrained()FK constraintChain after foreignId
->nullable()NULL allowedChain on any column
->default(value)DEFAULTChain on any column
->unique()UNIQUE indexChain on any column
->index()INDEXFor faster lookups
$table->json('col')JSONStore JSON data
$table->enum('status', [...])ENUMFixed list of values

4. Running Migrations

CommandWhat It Does
php artisan migrateRun all pending migrations
php artisan migrate:statusShow which migrations have run
php artisan migrate:rollbackUndo last batch of migrations
php artisan migrate:rollback --step=3Undo last 3 batches
php artisan migrate:resetUndo ALL migrations
php artisan migrate:freshDrop all tables + re-migrate
php artisan migrate:fresh --seedFresh + run seeders
php artisan migrate:refreshRollback all + re-migrate
migrate:fresh drops ALL tables — never run it on production!

5. Eloquent Model

<?php
namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Database\Eloquent\Factories\HasFactory;

class Post extends Model
{
    use HasFactory, SoftDeletes;

    // Columns allowed for mass assignment
    protected $fillable = ['title', 'slug', 'body', 'image', 'published', 'user_id'];

    // Columns hidden from JSON/array output
    protected $hidden = ['internal_notes'];

    // Automatic type casting
    protected $casts = [
        'published'    => 'boolean',
        'published_at' => 'datetime',
        'meta'         => 'array',    // JSON column auto-decoded
    ];

    // Custom table name (default = plural snake_case of class)
    // protected $table = 'blog_posts';

    // Accessor: $post->title returns title-cased value
    public function getTitleAttribute($value)
    {
        return ucwords($value);
    }

    // Mutator: auto-generate slug when title is set
    public function setTitleAttribute($value)
    {
        $this->attributes['title'] = $value;
        $this->attributes['slug']  = Str::slug($value);
    }
}

6. Eloquent CRUD Operations

Create

// Mass assignment (requires $fillable)
$post = Post::create([
    'title'   => 'My First Post',
    'body'    => 'Content here...',
    'user_id' => 1,
]);

// Set and save individually
$post = new Post();
$post->title = 'Another Post';
$post->body  = 'Content...';
$post->save();

Read

$posts = Post::all();                          // All records
$post  = Post::find(1);                        // Find by PK (null if not found)
$post  = Post::findOrFail(1);                  // Find or throw 404
$posts = Post::where('published', true)->get(); // WHERE clause
$post  = Post::where('slug', 'my-post')->first(); // First match
$count = Post::where('published', true)->count();
$posts = Post::latest()->paginate(10);         // Paginated results

Update

// Update via instance
$post = Post::findOrFail(1);
$post->update(['title' => 'Updated Title']);

// Mass update
Post::where('user_id', 5)->update(['published' => false]);

Delete

// Delete via instance
$post = Post::findOrFail(1);
$post->delete();

// Delete by condition
Post::where('published', false)->delete();

// Soft delete (if model uses SoftDeletes trait)
$post->delete();                  // sets deleted_at
Post::withTrashed()->get();       // include soft-deleted
Post::onlyTrashed()->get();       // only soft-deleted
$post->restore();                 // un-delete
$post->forceDelete();             // permanent delete

7. Eloquent Relationships

One to Many (hasMany / belongsTo)

// User hasMany Posts
class User extends Model {
    public function posts() {
        return $this->hasMany(Post::class);
    }
}

// Post belongsTo User
class Post extends Model {
    public function user() {
        return $this->belongsTo(User::class);
    }
}

// Usage
$posts = $user->posts;                          // all posts by user
$user  = $post->user;                          // post's author
$posts = $user->posts()->where('published', true)->get();

One to One (hasOne)

class User extends Model {
    public function profile() {
        return $this->hasOne(Profile::class);
    }
}
$profile = $user->profile;
$user    = $profile->user; // via belongsTo(User::class)

Many to Many (belongsToMany)

class Post extends Model {
    public function tags() {
        return $this->belongsToMany(Tag::class);
        // Needs pivot table: post_tag (post_id, tag_id)
    }
}

// Attach/detach tags
$post->tags()->attach([1, 3, 5]);
$post->tags()->detach(3);
$post->tags()->sync([1, 5]);      // replace all
$tagNames = $post->tags->pluck('name');

Eager Loading (prevent N+1 queries)

// BAD: N+1 queries (1 for posts + 1 per post for user)
$posts = Post::all();
foreach ($posts as $post) { echo $post->user->name; }

// GOOD: 2 queries total
$posts = Post::with('user')->get();
$posts = Post::with(['user', 'tags'])->get(); // multiple

8. Query Builder

use Illuminate\Support\Facades\DB;

// Basic select
$posts = DB::table('posts')->get();
$post  = DB::table('posts')->where('id', 1)->first();

// Chained methods
$posts = DB::table('posts')
    ->select('title', 'body', 'created_at')
    ->where('published', true)
    ->where('user_id', 1)
    ->orderBy('created_at', 'desc')
    ->limit(10)
    ->get();

// Joins
$posts = DB::table('posts')
    ->join('users', 'posts.user_id', '=', 'users.id')
    ->select('posts.title', 'users.name as author')
    ->get();

// Aggregates
$count = DB::table('posts')->where('published', true)->count();
$max   = DB::table('posts')->max('views');
$avg   = DB::table('posts')->avg('rating');

9. Seeders and Factories

Create a Seeder

php artisan make:seeder PostSeeder

// database/seeders/PostSeeder.php
class PostSeeder extends Seeder
{
    public function run(): void
    {
        Post::create([
            'title'   => 'Hello World',
            'body'    => 'First post content',
            'user_id' => 1,
            'published' => true,
        ]);
    }
}

// Run it
php artisan db:seed --class=PostSeeder

Model Factory (for fake test data)

php artisan make:factory PostFactory --model=Post

// database/factories/PostFactory.php
class PostFactory extends Factory
{
    public function definition(): array
    {
        return [
            'title'     => fake()->sentence(),
            'body'      => fake()->paragraphs(3, true),
            'slug'      => fake()->slug(),
            'published' => fake()->boolean(),
            'user_id'   => User::factory(), // creates a user too
        ];
    }
}

// DatabaseSeeder.php
Post::factory(50)->create(); // create 50 fake posts

// Run all seeders
php artisan db:seed

📌 Study Checklist