📦

Models & Migrations

Tạo Model với Migration

# Tạo model + migration + factory + seeder
php artisan make:model Post -mfs

# Tạo model với tất cả
php artisan make:model Product --all
<?php
// database/migrations/xxxx_create_posts_table.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();
            $table->foreignId('user_id')->constrained()->cascadeOnDelete();
            $table->foreignId('category_id')->nullable()->constrained();
            $table->string('title');
            $table->string('slug')->unique();
            $table->text('content');
            $table->enum('status', ['draft', 'published', 'archived'])->default('draft');
            $table->timestamp('published_at')->nullable();
            $table->timestamps();
            $table->softDeletes();
            
            // Indexes
            $table->index(['status', 'published_at']);
            $table->fullText(['title', 'content']);
        });
    }

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

Model Definition

<?php
// app/Models/Post.php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;

class Post extends Model
{
    use SoftDeletes;

    protected $fillable = [
        'title', 'slug', 'content', 'status',
        'category_id', 'published_at'
    ];

    protected $casts = [
        'published_at' => 'datetime',
        'status' => PostStatus::class, // Enum casting
    ];

    // Relationships
    public function author(): BelongsTo
    {
        return $this->belongsTo(User::class, 'user_id');
    }

    public function category(): BelongsTo
    {
        return $this->belongsTo(Category::class);
    }

    public function comments(): HasMany
    {
        return $this->hasMany(Comment::class);
    }

    public function tags(): BelongsToMany
    {
        return $this->belongsToMany(Tag::class)
            ->withTimestamps();
    }

    // Accessors & Mutators (Laravel 9+)
    protected function title(): Attribute
    {
        return Attribute::make(
            get: fn (string $value) => ucfirst($value),
            set: fn (string $value) => strtolower($value),
        );
    }

    // Scopes
    public function scopePublished($query)
    {
        return $query->where('status', 'published')
            ->whereNotNull('published_at');
    }

    public function scopeByCategory($query, int $categoryId)
    {
        return $query->where('category_id', $categoryId);
    }
}
🔍

Query Builder

Queries Thường Dùng

<?php

// Eager Loading (N+1 problem solution)
$posts = Post::with(['author', 'category', 'tags'])
    ->published()
    ->latest()
    ->paginate(15);

// Conditional queries
$posts = Post::query()
    ->when($request->category, fn($q, $cat) => $q->byCategory($cat))
    ->when($request->search, fn($q, $s) => $q->where('title', 'like', "%$s%"))
    ->when($request->sort === 'popular', 
        fn($q) => $q->orderByDesc('views'),
        fn($q) => $q->latest()
    )
    ->get();

// Aggregations
$stats = Post::query()
    ->selectRaw('status, COUNT(*) as count')
    ->groupBy('status')
    ->get();

// Subqueries
$users = User::addSelect([
    'latest_post_title' => Post::select('title')
        ->whereColumn('user_id', 'users.id')
        ->latest()
        ->limit(1)
])->get();

// Update with increment
Post::where('id', $id)->increment('views');

// Mass update
Post::where('status', 'draft')
    ->where('created_at', '<', now()->subDays(30))
    ->update(['status' => 'archived']);
💡 Performance: Luôn dùng with() để eager load relationships, tránh N+1 queries.
← Bài 1: Routing Bài 3: Authentication →