slug) { $model->slug = $model->generateSlug(); } }); static::updating(function ($model) { if ($model->isDirty('slug') && $model->getOriginal('slug')) { static::addToSlugHistory($model->getOriginal('slug')); } }); } /** * Get all slug histories for this model. */ public function slugHistories(): MorphMany { return $this->morphMany(SlugHistory::class, 'sluggable'); } /** * Add a slug to the history. */ protected function addToSlugHistory(string $slug): void { $this->slugHistories()->create(['slug' => $slug]); } /** * Generate a unique slug. */ protected function generateSlug(): string { $slug = Str::slug($this->name); $count = 2; while ( static::where('slug', $slug) ->where('id', '!=', $this->id) ->exists() || SlugHistory::where('slug', $slug) ->where('sluggable_type', get_class($this)) ->where('sluggable_id', '!=', $this->id) ->exists() ) { $slug = Str::slug($this->name) . '-' . $count++; } return $slug; } /** * Find a model by its current or historical slug. * * @param string $slug * @return static|null */ public static function findBySlug(string $slug) { // Try current slug $model = static::where('slug', $slug)->first(); if ($model) { return $model; } // Try historical slug $slugHistory = SlugHistory::where('slug', $slug) ->where('sluggable_type', static::class) ->latest() ->first(); if ($slugHistory) { return static::find($slugHistory->sluggable_id); } return null; } /** * Find a model by its current or historical slug or fail. * * @param string $slug * @return static * @throws \Illuminate\Database\Eloquent\ModelNotFoundException */ public static function findBySlugOrFail(string $slug) { return static::findBySlug($slug) ?? throw new \Illuminate\Database\Eloquent\ModelNotFoundException; } }