martinbean/laravel-sluggable-trait
Installation:
composer require martinbean/laravel-sluggable-trait
No additional configuration is required for basic usage.
Apply the Trait:
Add the Sluggable trait to your Eloquent model:
use MartinBean\Database\Eloquent\Sluggable;
class Post extends Model
{
use Sluggable;
}
This automatically generates a slug from the name column and stores it in the slug column.
First Use Case:
Save a model instance with a name attribute:
$post = new Post(['name' => 'How to Build a Laravel App']);
$post->save(); // Automatically generates slug: "how-to-build-a-laravel-app"
MartinBean\Database\Eloquent\Sluggable for default behavior.Default Behavior:
name column and stored in the slug column.Str::slug() for conversion (e.g., "Hello World" → "hello-world").Custom Slug Sources:
Override getSluggableString() to derive slugs from multiple fields:
protected function getSluggableString()
{
return "{$this->category->name} - {$this->title}";
}
Custom Slug Columns:
Override getSlugColumnName() to use a non-standard column:
protected function getSlugColumnName()
{
return 'url_friendly_name';
}
Manual Slug Updates: Force a slug update without saving the model:
$post->updateSlug(); // Manually trigger slug generation.
Form Handling: Use the trait with Laravel’s Form Request validation to ensure slug uniqueness:
public function rules()
{
return [
'name' => 'required|string',
'slug' => 'required|unique:posts,slug,' . $this->post->id,
];
}
API Responses:
Include slugs in API responses via App\Http\Resources\PostResource:
public function toArray($request)
{
return [
'id' => $this->id,
'title' => $this->name,
'slug' => $this->slug, // Automatically populated.
];
}
SEO Meta Tags: Dynamically generate meta titles/descriptions using the slug:
public function getMetaTitle()
{
return "{$this->name} | {$this->slug}";
}
Scoped Routes: Use slugs in route definitions:
Route::get('/posts/{post:slug}', [PostController::class, 'show']);
Observers/Events:
Listen for saved events to log slug changes:
public function saved(Model $model)
{
if ($model instanceof Post) {
Log::info("Slug updated to: {$model->slug}");
}
}
Visibility Change (v0.4.0+):
public methods (getSlugColumnName(), getSluggableString()) will fail.protected or alias the methods:
public function getSlugColumnName()
{
return $this->getSlugColumnNameProtected();
}
protected function getSlugColumnNameProtected() { ... }
Duplicate Slugs:
post-2).MartinBean\Database\Eloquent\Sluggable::generateSlug().Case Sensitivity:
Str::slug() behavior by extending the trait:
use MartinBean\Database\Eloquent\Sluggable as BaseSluggable;
class CustomSluggable extends BaseSluggable {
protected function generateSlug($string)
{
return Str::slug($string, '-', ['å' => 'a']); // Custom replacements.
}
}
Mass Assignment:
$fillable if needed:
protected $fillable = ['name', 'slug'];
Database Migrations:
$table->string('slug')->unique()->index();
Log Slug Generation:
Temporarily override generateSlug() to log input/output:
protected function generateSlug($string)
{
\Log::debug("Generating slug from: {$string}");
return parent::generateSlug($string);
}
Check for Overrides:
Use method_exists() to verify trait methods are not being overridden incorrectly:
if (method_exists($model, 'getSlugColumnName')) {
\Log::info("Custom slug column: {$model->getSlugColumnName()}");
}
Custom Slug Logic: Extend the trait to add validation or transformations:
class ExtendedSluggable extends BaseSluggable {
protected function getSluggableString()
{
$string = parent::getSluggableString();
return preg_replace('/[^a-z0-9\s-]/', '', $string); // Sanitize.
}
}
Soft Deletes: Handle slug uniqueness for soft-deleted models by excluding them from checks:
protected function getSlugColumnName()
{
return 'slug';
}
protected function getUniqueSlugConstraints()
{
return ['slug', $this->getKey(), 'deleted_at', null];
}
Localization:
Generate language-specific slugs by overriding generateSlug():
protected function generateSlug($string)
{
return Str::slug(app()->getLocale() === 'es' ? $string : __($string));
}
Caching: Cache slugs to avoid regeneration on every save:
public function save(array $options = [])
{
if (!$this->exists || $this->isDirty('name')) {
$this->slug = $this->generateSlug($this->getSluggableString());
}
return parent::save($options);
}
How can I help you explore Laravel packages today?