Creating clean and user-friendly URLs is an essential aspect of every website for SEO. Laravel provides a helper Illuminate\Support\Str::slug
for transforming string into URL-friendly slug, But this is not enough for our models to generate slugs automatically.
In this article, We'll create a custom HasSlug
trait that simplifies the process of generating slugs and explore how it can seamlessly enhance your Laravel models.
Defining the Custom HasSlug
Trait
Let's start by creating a custom HasSlug
trait:
namespace App\Concerns; // your namespace
trait HasSlug
{
protected function slugKey(): string
{
return 'slug';
}
abstract protected function sluggable(): string;
}
Here, the slugKey
method returns the name of the key that stores the slug. This method is used to determine the default key for the slug attribute. Default is 'slug'.
Also, a sluggable
abstract method, Which should be implemented by the model and return the name of the attribute that should be used for slug generation.
Boot Method for Slug Generation
Next, let's implement the boot method for automatic slug generation during model creation:
protected static function bootHasSlug()
{
static::creating(function (Model $model) {
if (! $model->{$model->slugKey()}) {
$model->{$model->slugKey()} = static::generateUniqueSlug($model->{$model->sluggable()});
}
});
}
This boot method ensures that HasSlug
trait takes action when a model is being created, automatically generating a unique slug if it is not already set.
Unique Slug Generation Logic
Let's implement the generateUniqueSlug
method that generates a unique slug based on the given string:
public static function generateUniqueSlug(?string $str): ?string
{
if (! $str) {
return null;
}
$counter = 1;
$strSlug = Str::slug($str);
$slug = $strSlug;
while (static::whereSlug($slug)->exists()) {
$slug = $strSlug.'-'.$counter++;
}
return $slug;
}
This method generates a unique slug based on the given string, checking if it already exists in the database. If it does, it appends a counter to the slug to generate a unique value.
Query Scopes and Finder Methods
Next, implement the whereSlug
scope and findBySlug
, findBySlugOrFail
methods:
public function scopeWhereSlug(Builder $query, string $slug): Builder
{
return $query->where($this->slugKey(), $slug);
}
public static function findBySlug(string $slug, array $columns = ['*']): static
{
return static::whereSlug($slug)->first($columns);
}
public static function findBySlugOrFail(string $slug, array $columns = ['*']): static
{
return static::whereSlug($slug)->firstOrFail($columns);
}
These methods simplify the process of querying models based on their slugs, making your code more readable and efficient.
Making it Model-Specific
To tie everything together, your model needs to implement the abstract sluggable
method:
abstract protected function sluggable(): string;
In your actual model, you specify which attribute should be used for slugging by returning its name in the sluggable
method.
Implementing in Your Model
Now, let's consider an example with a Post
model:
use App\Concerns\HasSlug;
class Post extends Model
{
use HasSlug;
protected $fillable = ['title', 'slug', 'body'];
protected function sluggable(): string
{
return 'title'; // Use the 'title' attribute for slugging
}
// Other model-specific code...
}
By implementing the HasSlug
trait and specifying the title
attribute for slugging, you enable slugging functionality effortlessly.
Example:
$post = Post::create([
'title' => 'My Post',
'body' => 'This is my post body.',
]);
echo $post->slug; // Output: my-post
$another = Post::create([
'title' => 'My Post',
'body' => 'Another post with same title.',
]);
echo $another->slug; // Output: my-post-1
So, now you can generate slugs for your models seamlessly by just using the HasSlug
trait and specifying the sluggable
attribute.
Conclusion
Creating a custom HasSlug
trait for slug generation allows for a tailored and simplified slug generation logic. It also simplifies the process of querying models based on their slugs, making your code more readable and efficient.
You can check out the Source Code for more details.
Top comments (0)