Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Slugger Bundle Laravel Package

gibilogic/slugger-bundle

gibilogic/slugger-bundle is a PHP bundle for generating clean, URL-friendly slugs from titles and text. It provides reusable slugging utilities and configurable behavior for Symfony-style applications, helping keep routes and content identifiers consistent.

View on GitHub
Deep Wiki
Context7

Getting Started

Know what you're actually getting. gibilogic/slugger-bundle is a Symfony 2 bundle, not a Laravel package. It does one thing: registers a small slugger service in the Symfony DI container so you can inject a slugify helper instead of calling a static function. There is no ServiceProvider, no facade, no config file to publish, and no Illuminate dependency anywhere in it. Last release was 2018-10-23, it targets Symfony 2 conventions (XML service definitions under Resources/config/), and it has zero stars and zero dependents. Treat it as a reference implementation to copy, not a dependency to composer require.

Where to look first. In the repo, open the service definition (Resources/config/services.xml or equivalent) to find the service id and any aliases, then open the slugger class itself — it will be one class with essentially one public method (slugify($string, $separator = '-')). The entire useful surface area is under ~50 lines. Confirm whether it delegates to iconv/intl transliteration or hand-rolled str_replace maps, because that determines output quality for non-ASCII input.

Minimal Laravel path (recommended):

  1. Copy the slugger class into app/Support/Slugger.php, swap any Symfony-specific type hints for plain PHP.
  2. Register it once:
    // AppServiceProvider::register()
    $this->app->singleton(\App\Support\Slugger::class);
    
  3. Use it:
    app(\App\Support\Slugger::class)->slugify('Hello, World!'); // hello-world
    

If you genuinely want to pull the vendor package in for study, expect PHP/constraint conflicts with modern Laravel; you can composer require with --ignore-platform-reqs in a scratch project, but do not ship it.

First real use case. Generating a URL-safe slug from a title column on save, then routing to records by slug instead of id. That's the 90% case and it's what the next section covers.

Implementation Patterns

Bind, don't new. The only thing the bundle buys you is injectability, so preserve that in Laravel. Depend on your own interface (App\Contracts\Slugifier) and let the copied class implement it — that way if you later swap to Str::slug() or cocur/slugify, nothing in your domain code changes.

A trait for the Eloquent side. This is the pattern that replaces what the bundle would do for you:

trait HasSlug
{
    public static function bootHasSlug(): void
    {
        static::saving(function ($model) {
            if (blank($model->slug) || $model->isDirty($model->slugSource())) {
                $model->slug = $model->generateSlug();
            }
        });
    }

    protected function slugSource(): string
    {
        return 'title';
    }

    protected function generateSlug(): string
    {
        return app(Slugifier::class)->slugify($this->{$this->slugSource()});
    }
}

Uniqueness — pick a strategy deliberately. Three viable ones:

  • Append a counter (post-2, post-3): loop while static::where('slug', $candidate)->exists(), then rely on a unique DB index plus a retry-on-QueryException wrapper, because the loop is racy under concurrency.
  • Append a short suffix (post-a1b2c3): deterministic enough and collision-free in practice, but uglier URLs.
  • Slug history table: never mutate an existing slug; insert a new row into slugs (slug, sluggable_id, sluggable_type) and 301 from old to new. Best for SEO, most work.

Route model binding. Once slugs exist, getRouteKeyName(): string { return 'slug'; } gives you /posts/hello-world for free. Keep the numeric id available for admin routes so a slug rename never breaks internal links.

Batch backfill. For existing rows, write an artisan command using chunkById(500, ...) and saveQuietly() inside the loop so you don't fire observers, then run the uniqueness pass separately. Never Model::all() in a backfill.

Non-model use cases are where a standalone service still earns its keep: sitemap URL generation, importers that build filenames, CMS page paths, image/file naming. In those places call the service directly rather than reaching for Eloquent.

Multi-locale. Slugify per locale and store per-locale columns (slug_en, slug_fr) or a translations table row per locale; don't try to make one slug serve every language, and don't re-slugify on read.

Gotchas and Tips

  • Don't register the bundle. There is no Laravel bridge. SluggerBundle::class in config/bundles.php is meaningless here; the XML DI definitions won't be parsed by Laravel's container.
  • Abandoned dependency = your maintenance burden. No PHP 8 guarantees, no security patches, no Symfony 4+ support. Vendoring the ~50 lines is strictly better than adding it to composer.json.
  • Transliteration is environment-dependent. Output for Crème Brûlée or 北京 depends on whether intl/iconv is compiled in, and on locale settings. Pin behavior in a unit test with a fixed set of inputs rather than trusting it.
  • Empty results are real. Non-Latin scripts, emoji, or punctuation-only titles can slugify to ''. Always fall back to Str::random()/UUID or the record id, and reject empty slugs before insert.
  • Index/collation mismatches. A unique index on a utf8mb4_unicode_ci column treats Hello and hello as the same slug — usually what you want, but be explicit. Also cap the slug column length (191 for older MySQL utf8mb4 indexes) and truncate generated slugs to match, or inserts will fail intermittently.
  • Observers don't cover everything. saving fires on save()/create(), not on update(), insert(), or raw query-builder writes. Mass imports and admin bulk actions will silently produce null slugs; guard the column as NOT NULL so failures are loud.
  • Don't slugify in an accessor. It runs on every attribute read, including serialization and eager-loaded collections — a hidden N+1 of string work.
  • Reserved words. admin, api, login, new, edit as slugs will collide with your route table. Either prefix routes (/p/{slug}) or blocklist those values.
  • Debugging tip. When a slug looks wrong, diff your service against Str::slug($input) in tinker — Str::slug is the de facto baseline in Laravel and any divergence is either a feature you want or a bug you inherited from the 2015 implementation.
  • Keep the seam. Whatever you copy, wrap it in App\Contracts\Slugifier. If you skip this, a future migration off this code touches every model and controller that calls it.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums
rlacerda83/query-parser
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings