Models, Relationships, Query Builder trong Laravel
# 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');
}
};
<?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);
}
}
<?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']);
with() để eager load
relationships, tránh N+1 queries.