Objectif

ModΓ©liser un blog complet avec Eloquent : Articles, CatΓ©gories, Tags (N:N), Commentaires, Utilisateurs. ImplΓ©menter les relations, factories, seeders, scopes et observers.

ConceptMis en pratique
RelationshasMany, belongsTo, belongsToMany, hasManyThrough
N+1 fixwith('author', 'tags', 'category') systΓ©matique
ScopesscopePublished, scopeFeatured, scopeByCategory
FactoriesÉtats published/draft/featured, relations via for()
ObserverAuto-gΓ©nΓ©ration du slug, cascade delete commentaires
Soft deletesRestauration d'articles supprimΓ©s

SchΓ©ma de base de donnΓ©es

users          articles          categories
─────────      ─────────────     ──────────────
id             id                id
name           title             name
email          slug              slug
password       excerpt           description
role           body              created_at
               status
               is_featured       tags
               published_at      ─────────
               author_id β†’ users id
               category_id β†’ categories name
               created_at        color
               deleted_at (soft)

article_tag (pivot)     comments
───────────────────     ─────────────────
article_id              id
tag_id                  body
position                article_id β†’ articles
                        user_id β†’ users
                        approved
                        created_at

Consignes

  1. CrΓ©er les migrations pour les 5 tables (users dΓ©jΓ  prΓ©sent)
  2. CrΓ©er les models avec les relations, casts, fillable, scopes
  3. Γ‰crire ArticleFactory avec les Γ©tats published(), draft(), featured()
  4. Γ‰crire DatabaseSeeder : 1 admin, 10 users, 5 catΓ©gories, 10 tags, 50 articles publiΓ©s avec tags alΓ©atoires et 3-5 commentaires chacun
  5. Γ‰crire ArticleObserver : gΓ©nΓ©ration du slug Γ  la crΓ©ation, mise Γ  jour si le titre change
  6. Dans le controller, toujours eager-loader avec with()

Solution commentΓ©e

// app/Models/Article.php
namespace App\Models;

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

class Article extends Model
{
    use HasFactory, SoftDeletes;

    protected $fillable = ['title', 'slug', 'excerpt', 'body', 'status', 'is_featured', 'published_at', 'author_id', 'category_id'];

    protected $casts = [
        'published_at' => 'datetime',
        'is_featured'  => 'boolean',
    ];

    // ── Relations ─────────────────────────────────────────
    public function author()    { return $this->belongsTo(User::class, 'author_id'); }
    public function category()  { return $this->belongsTo(Category::class); }
    public function comments()  { return $this->hasMany(Comment::class); }
    public function tags()      {
        return $this->belongsToMany(Tag::class)
            ->withTimestamps()
            ->withPivot('position')
            ->orderByPivot('position');
    }

    // ── Scopes ────────────────────────────────────────────
    public function scopePublished(Builder $q): void {
        $q->where('status', 'published')->whereNotNull('published_at');
    }

    public function scopeFeatured(Builder $q): void {
        $q->where('is_featured', true);
    }

    public function scopeByCategory(Builder $q, int $categoryId): void {
        $q->where('category_id', $categoryId);
    }

    // ── Helpers ───────────────────────────────────────────
    public function isPublished(): bool { return $this->status === 'published'; }
}

// app/Observers/ArticleObserver.php
class ArticleObserver
{
    public function creating(Article $article): void {
        $article->slug = \Str::slug($article->title);
        // S'assurer de l'unicitΓ© du slug
        $count = Article::withTrashed()->where('slug', 'like', $article->slug . '%')->count();
        if ($count > 0) $article->slug .= '-' . ($count + 1);
    }

    public function updating(Article $article): void {
        if ($article->isDirty('title')) {
            $article->slug = \Str::slug($article->title);
        }
    }

    public function deleting(Article $article): void {
        $article->comments()->delete();
        $article->tags()->detach();
    }
}

// Enregistrement dans AppServiceProvider::boot()
// Article::observe(ArticleObserver::class);
// database/factories/ArticleFactory.php
class ArticleFactory extends Factory
{
    public function definition(): array {
        return [
            'title'        => $this->faker->sentence(6),
            'excerpt'      => $this->faker->paragraph(2),
            'body'         => $this->faker->paragraphs(5, true),
            'status'       => 'draft',
            'is_featured'  => false,
            'published_at' => null,
            'author_id'    => User::factory(),
            'category_id'  => Category::factory(),
        ];
    }

    public function published(): static {
        return $this->state([
            'status'       => 'published',
            'published_at' => $this->faker->dateTimeBetween('-6 months', 'now'),
        ]);
    }

    public function featured(): static {
        return $this->state(['is_featured' => true]);
    }
}

// database/seeders/DatabaseSeeder.php
public function run(): void {
    $admin = User::factory()->create([
        'name'  => 'Admin',
        'email' => 'admin@test.com',
        'role'  => 'admin',
    ]);
    User::factory()->count(10)->create();

    $categories = Category::factory()->count(5)->create();
    $tags       = collect(['PHP', 'Laravel', 'Eloquent', 'API', 'Docker', 'Tests', 'Blade', 'Queue', 'Redis', 'MySQL'])
        ->map(fn($name) => Tag::create(['name' => $name, 'color' => '#' . fake()->hexColor()]));

    Article::factory()
        ->count(50)
        ->published()
        ->recycle($categories)
        ->create()
        ->each(function (Article $article) use ($tags) {
            // Attacher 1-3 tags alΓ©atoires
            $article->tags()->attach(
                $tags->random(rand(1, 3))->pluck('id')
            );
            // Ajouter 3-5 commentaires
            Comment::factory()
                ->count(rand(3, 5))
                ->create(['article_id' => $article->id]);
        });
}
← Cours Module 04 🧠 QCM Module 05 β†’