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

Uniqueslugbundle Laravel Package

fbeen/uniqueslugbundle

fbeen/uniqueslugbundle is a Symfony bundle that generates unique, URL-friendly slugs for Doctrine entities. It helps prevent duplicate slugs, supports SEO-friendly URLs, and integrates with entity lifecycle events for automatic slug creation and updates.

View on GitHub
Deep Wiki
Context7

Getting Started

This is a Symfony bundle, not a Laravel package. If you're evaluating it from a Laravel codebase, composer require fbeen/uniqueslugbundle will install it but nothing will wire up — there's no service provider, no facade, no config publish. Your realistic options are (a) use it in a Symfony app, or (b) read its ~few hundred lines of source and lift the slug-uniqueness logic into a Laravel SlugService. Option (b) is often the right call given the package's age.

Assuming a Symfony app:

  1. composer require fbeen/uniqueslugbundle
  2. Register the bundle. With Flex (config/bundles.php):
    return [
        // ...
        Fbeen\UniqueSlugBundle\FbeenUniqueSlugBundle::class => ['all' => true],
    ];
    
    On legacy Symfony, add it to AppKernel::registerBundles().
  3. Look first at the bundle's own source, not just the README. In order of usefulness: Resources/config/services.yaml (or DependencyInjection/) to find the service ID and its class, then that class to read the actual method signature, then DependencyInjection/Configuration.php for any configurable separator/length/transliteration options. The README is thin; the service class is the documentation.
  4. First use case: give a Post (or Page) entity a unique, URL-safe slug derived from its title at creation time.
// src/Controller/PostController.php
public function create(Request $request, EntityManagerInterface $em, /* slugger service */ $slugger)
{
    $post = new Post();
    $post->setTitle($request->request->get('title'));

    // Shape is illustrative — confirm the exact method name/args against the
    // bundle's service class. It's essentially (entity, sourceString, property).
    $post->setSlug($slugger->uniqueSlug($post, $post->getTitle(), 'slug'));

    $em->persist($post);
    $em->flush();
}

Add a unique index on the column in the same migration so the DB enforces what the bundle only checks:

/** @ORM\Column(type="string", length=191, unique=true) */
private $slug;

Implementation Patterns

Explicit generation in the controller/service layer. Best starting point: easy to debug, easy to override for imports or admin edits. Generate only when the slug is empty or the source field actually changed — don't regenerate on every save or you silently break every inbound link.

Doctrine event subscriber for automatic slugs. This is the pattern most teams settle on. Inject the slugger into a subscriber rather than trying to put service calls in entity lifecycle callbacks (entities can't cleanly hold services).

// src/EventListener/SlugSubscriber.php
class SlugSubscriber implements EventSubscriber
{
    public function __construct(private $slugger) {}

    public function getSubscribedEvents(): array
    {
        return [Events::prePersist, Events::preUpdate];
    }

    public function prePersist(LifecycleEventArgs $args): void
    {
        $entity = $args->getObject();
        if (!$entity instanceof SluggableInterface || $entity->getSlug() !== null) {
            return;
        }
        $entity->setSlug($this->slugger->uniqueSlug($entity, $entity->getSlugSource(), 'slug'));
    }
}
# config/services.yaml
App\EventListener\SlugSubscriber:
    arguments: ['@fbeen_unique_slug.slugger']   # verify the service ID
    tags: ['doctrine.event_subscriber']

Define a tiny SluggableInterface (getSlugSource(), getSlug(), setSlug()) in your app so the subscriber stays generic and you aren't coupled to the bundle's own trait.

Form types / manual override. Generate a suggestion for the slug field on the create form, let the editor override it, and on edit only regenerate if the title changed and the slug hasn't been manually pinned. A slugLocked boolean is cheaper than explaining to users why their URL changed.

Route slugs (the "routes" half of the description). Use the same uniqueness guarantee for CMS-style paths — pages, categories, landing pages — then look up by slug instead of ID:

/**
 * @Route("/p/{slug}", name="page_show")
 */
public function show(string $slug, PageRepository $pages, EntityManagerInterface $em)
{
    $page = $pages->findOneBy(['slug' => $slug]) ?? throw $this->createNotFoundException();
    return $this->render('page/show.html.twig', ['page' => $page]);
}

Bulk imports and fixtures. For a CSV import of 5,000 rows, the per-entity uniqueness check costs a query each. Either pre-load existing slugs into an in-memory array_flip set and generate suffixes locally, or flush in chunks and accept the query cost. Don't call the slugger 5,000 times inside a single un-flushed unit of work — you'll get duplicate suffixes because the earlier inserts aren't visible to the check.

Testing. Point the slugger at an in-memory SQLite database in a unit test and assert the collision behaviour (foo, foo-1, foo-2) rather than testing it through the UI. The suffixed-slug rules are the only non-obvious logic in the package.

Gotchas and Tips

  • Framework mismatch is the headline risk. Symfony bundle, zero Laravel relevance. Even in Symfony, the last release is 2019-11-05 — check composer.json constraints before adopting. Expect PHP 7-era signatures, YAML/annotation DI, and possible breakage on Symfony 6/7 (service names, EventSubscriber interfaces, autowiring changes). Budget an hour to read the source and fork it if needed; it's small.
  • 4 stars / 0 dependents = effectively unmaintained. Treat it as a reference implementation with a convenience wrapper, not as infrastructure. Never let a critical-path feature depend on it without pinning the version.
  • Check-then-insert is racy. Two concurrent requests for "hello-world" can both pass the uniqueness check. Keep the DB unique index and catch UniqueConstraintViolationException, then retry with the next suffix. The bundle cannot solve this for you.
  • preUpdate and Doctrine change sets. Doctrine computes the change set before preUpdate, and changes made inside the listener don't propagate reliably to the UPDATE. Prefer generating the slug before flush() (setter, controller, or a preFlush listener) rather than mutating the entity in preUpdate.
  • Soft deletes and scoping. Verify whether the uniqueness check counts soft-deleted rows, and whether it's global or scoped. A slug freed by a deleted post may be silently reused, resurrecting 404s from old links — or may be permanently blocked, which is just as annoying. Test both.
  • Column length + suffix overflow. strlen(base) + 2 can exceed the column. Set length: 191 (or 255) and truncate the base string before appending the suffix, or you'll get a truncation error on a 250-character German title.
  • Non-Latin titles can produce an empty slug. Transliteration often fails for CJK, Cyrillic, or emoji-heavy strings. Add a fallback (random token or id-based slug) rather than letting an empty string hit the unique index — the first one succeeds and every subsequent one explodes.
  • Changing a published slug breaks inbound links. Keep a slug_history table and 301-redirect old slugs to the current one, or lock the slug after publish. This is the single most common production regret with slug generators.
  • Extension points: the slugger is almost certainly a plain service. Use Symfony's service decoration (decorates: fbeen_unique_slug.slugger) to swap in your own transliteration or suffix strategy instead of editing vendor code. If there's a Configuration.php, that's where separator/prefix/length keys live.
  • Debugging tip: when a slug comes out wrong, dump the raw title and the transliterated form separately. The bug is nearly always upstream in the source string (HTML entities, trailing whitespace,   from a rich-text editor) rather than in the uniqueness logic.
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.
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
robertmarney/lara-hierarchial-collections
simonschaufi/rule-doc-generator
emissaoja/emissaoja
blackbricksoftware/gravityforms-civicrm-form-processor
al-rahabi/fancy-crud
jinas/dhivatar-component
mltstephane/laravel-quality
bernskiold/laravel-talentlms
bernskioldmedia/laravel-form-architect