Simplify Slug Creation for Eloquent Models in Laravel
Tags: laravel, php, eloquent, slug
Published: 2024-01-10 17:16:16

---
title: Simplify Slug Creation for Eloquent Models in Laravel
published: true
date: 2024-01-09 00:00:00 UTC
tags: laravel, php, eloquent, slug
canonical_url: https://raziul.dev/simplify-slug-creation-for-eloquent-models/
---

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:

```php
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:

```php
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:

```php
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:

```php
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:

```php
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:

```php
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:

```php
$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](https://github.com/iRaziul/larabits/blob/main/eloquent/traits/HasSlug.php) for more details.