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

Routing Auto Laravel Package

symfony-cmf/routing-auto

Symfony CMF RoutingAuto automatically creates and updates routes for your content objects. It hooks into persistence events and uses configurable route generators, keeping URLs in sync with your documents without any manual route management.

View on GitHub
Deep Wiki
Context7

Getting Started

This is a Symfony CMF component, not a Laravel package — the mental model is closest to Eloquent observers + a catch-all route that resolves a slug from the database, except the "database" is a Doctrine AutoRoute table maintained by event listeners. Read the code with that mapping in mind:

RoutingAuto concept Laravel equivalent
RoutingAutoManager an observer/orchestrator service
RouteMaker the code in your saved observer
AutoRouteProvider a catch-all route resolver
UriGenerator + token providers spatie/laravel-sluggable + a URL builder
Defunct routes old slugs kept for 301s

Minimum viable setup (Symfony app):

  1. Install both halves — the component is inert without the bundle that wires the DI container, Doctrine mapping and compiler passes:
    composer require symfony-cmf/routing-auto symfony-cmf/routing-auto-bundle
    
    Pin the versions. The last release is 2020-10-28 and the repo is archived; expect Symfony 3.4/4.x-era constraints and doctrine/* conflicts on modern projects.
  2. Register CmfRoutingAutoBundle in config/bundles.php (or AppKernel), and make sure the AutoRoute entity/document mapping is loaded (doctrine.orm.mapping / auto-mapping in the bundle config).
  3. Create the storage: bin/console doctrine:schema:update --force or generate a migration for the auto_route table (columns for path, locale, content_class, content_id, type, name).
  4. Map exactly one class first:
    cmf_routing_auto:
        auto_route_mapping:
            App\Entity\Article:
                route_schema:
                    path: /articles/[content_method:method=getTitle]
    
  5. Persist an Article with a title, then check SELECT * FROM auto_route; and hit the URL through the CMF chain router.

First use case to prove out: create → edit title → confirm the old row becomes "defunct" and the new path works. That one loop exercises the manager, URI generator, conflict resolver and defunct handler together, and it's where 90% of the surprises live.

Read these classes first, in this order: RoutingAutoManager, RouteMaker, UriGenerator, AutoRouteProvider, then the bundle's DependencyInjection/Configuration.php — that last file is the only reliable documentation for the config keys.

Implementation Patterns

Token-driven route schemas. The path is a template resolved by token providers at persist time. The built-ins are a content-method provider (call a getter), a content-object provider (the object itself, usually for the ID) and a locale provider; the syntax is roughly [content_method:method=getTitle], [content_object], [content_locale]. Build the path from stable, already-slugified getters — never from getBody() or anything user-editable in a way that changes wildly.

Custom token providers. Implement TokenProviderInterface (provideValue(array $arguments, $object, $env) returning a string or null) and register it as a service tagged for the bundle's compiler pass. Typical uses: a getSlug() accessor, a SKU, a category hierarchy segment, or a translated title.

Nested content / parent paths. For a tree (Category → Page → Article), chain routes through the content path config so a child inherits the parent's URI. This forces a top-down persistence discipline: a child cannot resolve its parent's path if the parent has no AutoRoute yet. In seeders and importers, iterate depth-first and flush per level; never flush() a whole graph and hope the listener ordering works out.

Multi-locale / multi-schema. Declare several named schemas per class (one per locale, or a "short" and "canonical" URL). Each becomes its own AutoRoute row with a different name/locale. Pick the active one in a request listener or via the locale on the content object.

Conflict resolution. Two articles titled "Hello World" will collide. Configure the conflict resolver (auto_increment-style suffixing vs. throw) and make the decision deliberately: silent suffixing is great for CMS authors and terrible for SEO audits. Whatever you choose, log conflicts — the resolver firing is usually a symptom of a missing unique field in the schema.

Defunct routes are the whole point — don't delete them. The default handler tends to clean up old routes; keep them, store a timestamp, and resolve them in a kernel.exception listener (or Laravel: a 404 handler) to issue a 301 to the current route for the same content. Without this, every title edit is a broken link.

Keep the write path transactional and single-flighted. The manager persists routes in response to Doctrine lifecycle events, which means a listener can flush and re-trigger events. Wrap content saves in one transaction/unit of work and add a re-entrancy guard (a flag on the manager or a decorator) if you see duplicate auto_route rows. On Laravel you'd do this with DB::transaction() + withoutEvents() around bulk imports.

Custom storage. If your content lives outside Doctrine ORM/PHPCR-ODM, implement AdapterInterface (the adapter is what translates content objects into the metadata the manager needs). This is the sanctioned extension point and the cleanest way to reuse the URI/conflict machinery.

Testing. Unit-test the token providers and conflict resolver in isolation; for the rest, write a functional test that boots the kernel, persists an object, and asserts on the auto_route row count, path, and defunct status. Assertions against the repository are far more stable than asserting on the router.

Gotchas and Tips

  • Archived + zero dependents is the headline risk. No security patches, no PHP 8 / Symfony 5+ compatibility work, and the sibling symfony-cmf/routing is archived too. Treat it as vendored code you now own: fork it, or budget for extraction.
  • There is no debug:router story that helps you. Routes are database rows, not compiled definitions. Debug by querying auto_route directly and calling the manager's URI generation in a console command or dump() in the listener.
  • postPersist vs postUpdate timing. On postPersist the ID exists but the transaction is still open; on postUpdate the old values are already gone, so you must read getEntityChangeSet() (or the equivalent) to know the previous slug before it's overwritten. Capture the old path in a preUpdate listener if you need redirects.
  • Nulls from token providers silently produce ugly paths (/articles//, doubled separators, or empty segments). Return '', normalize, and assert the final URI is non-empty and starts with / before persisting.
  • Slugification is your job. The component builds the path from whatever the getter returns. Run titles through a slugifier/transliterator first; otherwise you get encoded UTF-8, spaces, and slashes (a / in a title will create phantom path segments).
  • Route cache. AutoRouteProvider caches lookups by route name; in dev, changes made directly in SQL won't appear until you clear it. In prod, warm the cache after content imports.
  • Circular event loops. Persisting a route fires Doctrine events; if your content listener reacts to all entities rather than the mapped class, you'll recurse. Scope listeners to specific classes.
  • Config keys drift from the docs. The mapping schema (route_schema, content_path, exists_action / not_exists_action, conflict_resolver) has changed across versions and the README lags. DependencyInjection/Configuration.php is the source of truth for the version you pinned — verify keys there before debugging why nothing is generated.
  • The exists_action/not_exists_action pair is the fix for importers. use existing routes rather than recreating the tree on every run; create only when you genuinely want new nodes. Getting this wrong duplicates whole subtrees on re-import.
  • Extension points worth knowing: TokenProviderInterface, ConflictResolverInterface, DefunctRouteHandlerInterface, and AdapterInterface. Those four cover essentially every customization need — you should rarely need to subclass the manager itself.
  • Migration advice for a Laravel codebase: don't port this. Model it as two columns (slug unique, path), a Route model with a saved observer generating the canonical URL, a Route::get('{path}')->where('path', '.*') fallback, and a route_redirects table written in the same transaction. You'll get the same behavior in ~150 lines with none of the archived-dependency risk.
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