Skip to content

Conversation

theHocineSaad
Copy link
Contributor

This PR introduces support for generating unique slugs in the Str::slug method.

Example:

// Generates a unique slug for the 'posts' table.
$slug = Str::slug('Hello World', '-', unique: true, table: 'posts');

// If 'hello-world' already exists, it will return 'hello-world-1', 'hello-world-2', etc.

New Parameters:

  • bool $unique = false: Enable unique slug generation.
  • ?string $table = null: Specify the table to check for uniqueness.
  • string $column = 'slug': Specify the column to check for uniqueness.
  • string|int|null $ignoreId = null: Optionally ignore a specific record by ID.

How It Works:

When $unique is true, the method checks the database for existing slugs.
If a record with the same slug is found, it appends an incrementing number to the slug until a unique value is found.
If $ignoreId is set, the query will exclude that record from the uniqueness check.

The changes are fully backward compatible.
All newly introduced parameters are optional and have default values that preserve the original behavior of the method.

@shaedrich
Copy link
Contributor

While this has its use cases, I'm not sure if the Str facade or the \Illuminate\Support namespace as a whole is the right place for this as you explicitly utilize the database instead of caching the generated slugs locally in the class. Therefore, this functionality would be better placed in the \Illuminate\Database or even \Illuminate\Database\Eloquent namespace to use this on models and determine the table name automatically

@theHocineSaad theHocineSaad marked this pull request as draft October 11, 2025 21:09
@hctorres02
Copy link

When $unique is true, the method checks the database for existing slugs.

I'm not sure, but when this is being used by accessor and mutator wouldn't it cause unnecessary demand on the database?

@NickSdot
Copy link
Contributor

NickSdot commented Oct 12, 2025

While this has its use cases, I'm not sure if the Str facade or the \Illuminate\Support namespace as a whole is the right place for this as you explicitly utilize the database instead of caching the generated slugs locally in the class. Therefore, this functionality would be better placed in the \Illuminate\Database or even \Illuminate\Database\Eloquent namespace to use this on models and determine the table name automatically

Adding to the above, \Illuminate\Support doesn't depend on \Illuminate\Database. So this PR will break illuminate components.

This probably should rather be an after validation feature or something.

I like the idea, though.

@antonkomarev
Copy link
Contributor

If you need this feature, just make a service class for it. Keep slug generation more simple. Otherwise it becomes overcomplicated and introduces redundant dependency.

@theHocineSaad
Copy link
Contributor Author

@shaedrich @hctorres02 @NickSdot @antonkomarev @taylorotwell

Appreciate the feedback, everyone, you’re right, the Str methods should stay simple.

I’ve thought of two possible approaches. Let me know what you think.

  1. Add unique_slug() helper method that returns a unique slug, but I am unsure whether it should belong in Illuminate/Support/helpers.php or Illuminate/Foundation/helpers.php.

  2. Add HasUniqueSlug trait which will be used like this:

use Illuminate\Database\Eloquent\Concerns\HasUniqueSlug;

class Post extends Model
{
    use HasUniqueSlug;

    protected $slugColumn = 'slug'; // optional, defaults to 'slug'
    protected $slugSourceColumn = 'title'; // optional, defaults to 'title'
}

Then, the trait automatically assigns a unique slug value during the model’s creating event.

@shaedrich
Copy link
Contributor

shaedrich commented Oct 12, 2025

You're welcome 👍🏻

I'd say, both concepts have their own value. However, the latter is more likely to be rejected by Taylor in favor of a separate package if there isn't any already. In case of the former, I submitted my namespacing preference in my post above. I don't think that either Illuminate/Support/helpers.php or Illuminate/Foundation/helpers.php would be the right place for it as it is dependent on Laravel's database functionality.

@henzeb
Copy link
Contributor

henzeb commented Oct 12, 2025

I like the has uniqueslug trait. But that trait is so simple that it doesn't need to be in the framework or package. It's a simple Attribute with a setter or getter.

@hctorres02
Copy link

You can achieve the same behavior using Str::macro, making its usage more explicit, instead of modifying the original method. Each method has its own responsibilities.

use Illuminate\Support\Str;
use Illuminate\Support\Facades\DB;

Str::macro('slugUnique', function ($value, $table, $column = 'slug') {
    $slug = Str::slug($value);

    if (DB::table($table)->whereExists($column, $slug)) {
        throw new Exception("The '{$slug}' slug already used!");
    }

    return $slug;
});

What worries me most is the return type. Would it throw an exception (example) or false, similar to strpos and so many other methods?

@donnysim
Copy link
Contributor

This definitely should not be in a Str class and it has serious drawbacks:

  1. you can't specify the connection
  2. it doesn't apply global model scopes
  3. you can't specify your own additional where conditions

Most of the time it's just better to have this logic on the model, service or application helper.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants