DEV Community

Cover image for Simplify Slug Creation for Eloquent Models in Laravel
Raziul Islam
Raziul Islam

Posted on • Originally published at raziul.dev on

Simplify Slug Creation for Eloquent Models in Laravel

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;
}
Enter fullscreen mode Exit fullscreen mode

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()});
        }
    });
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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...
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)