modera/routing-bundle
Modera Routing Bundle adds flexible route management to PHP applications, with support for localized and dynamic routes, route caching, and easy integration into existing frameworks. It simplifies defining, matching, and generating URLs while keeping configuration clean and maintainable.
Start by reading, not installing. modera/routing-bundle is a Symfony bundle targeting the Symfony 2.3–3.x era (PHP 5.5/7.0 constraints, last tag June 2016, zero dependents). It is not installable in a Laravel app and will not resolve against PHP 8.x or Symfony 4+. The day-to-day leverage comes from the pattern, not the dependency.
Where to look first (in the repo):
Modera\RoutingBundle\ModeraRoutingBundle or similar) — shows how it hooks the bundle lifecycle.composer.json — confirm version pins before you consider vendoring anything.First use case (legacy Symfony 2.x/3.x only): stop editing app/config/routing.yml for every bundle. Import the bundle's routing resource once, then let each installed bundle self-declare its routes. The host app's routing config becomes stable regardless of how many modules are installed.
First use case, adapted to Laravel (the one you'll actually do): in the package's service provider, replace the host-edit dance with auto-loaded routes:
// packages/acme/reports/src/ReportsServiceProvider.php
public function boot(): void
{
$this->loadRoutesFrom(__DIR__.'/../routes/web.php');
Route::middleware(config('reports.middleware', ['web']))
->prefix(config('reports.prefix', 'reports'))
->name('reports.')
->group(__DIR__.'/../routes/api.php');
}
That is functionally equivalent to what the bundle provides for Symfony, with zero third-party surface and full compatibility with every supported Laravel version.
Pattern 1 — Package-owned routes via auto-discovery (Laravel). Each package ships its own route files and registers them in boot() with loadRoutesFrom(). The host application never edits routes/web.php for a package. This is the direct analogue of the bundle's "bundle registers its own routing resource" behavior.
Pattern 2 — Config-driven route grouping. Wrap the package's route files in middleware/prefix/name groups driven by publishable config so installers can adjust behavior without touching provider code:
Route::middleware(config("{$package}.middleware"))
->prefix(config("{$package}.prefix"))
->group(__DIR__.'/../routes/web.php');
Pattern 3 — Feature-flag-gated registration. Only load routes when the module is enabled, mirroring Symfony's conditional bundle registration:
if (config('reports.enabled', false)) {
$this->loadRoutesFrom(__DIR__.'/../routes/web.php');
}
Pattern 4 — Symfony ≥ 5.3 replacement. Use AbstractBundle::configureRoutes(RoutingConfigurator $routes) to self-register routes; the framework imports them automatically, no host edit needed.
class AcmeBundle extends AbstractBundle
{
public function configureRoutes(RoutingConfigurator $routes): void
{
$routes->import(__DIR__.'/../config/routes.php');
}
}
Pattern 5 — Symfony < 5.3 replacement (legacy only). Implement PrependExtensionInterface and prepend a framework.router.resource entry pointing at the bundle's routing file. This is exactly what ModeraRoutingBundle automates.
Workflow for evaluation: spike the native replacement in one package, contract-test the resulting route names/prefixes/middleware/ordering against your current php artisan route:list output, and only reach for the bundle as a vendored fork if the spike fails on a hard constraint.
Integration tip — collision hygiene. Register a route-name prefix (->name('reports.')) on every package group. This is the single most valuable guardrail in either framework and it costs one line.
Framework mismatch is structural, not cosmetic. There is no port path: Symfony bundles, DI compiler passes, and routing resource loaders have no Laravel equivalents. Do not attempt composer require with --ignore-platform-reqs; that shifts the failure to runtime container compilation, i.e. deploy time.
The 9.65 opportunity score is a false positive. It signals an unmet need (dynamic route registration) — a real problem — attached to a dead artifact. Score it as reject / replace natively.
Route cache invalidation. Both Symfony's router:cache and Laravel's route:cache are compile-time. Any change to how packages register routes requires a warm-up step in deploy. A deploy that skips php artisan route:cache (or Symfony's cache warm-up) will silently serve the old table.
Silent route shadowing. Dynamically registered routes compete with host-app routes. In Symfony, later imports can override earlier ones; in Laravel, more specific routes must precede wildcards if you want them matched. There is no build-time diagnostic for either. Use name prefixes and test precedence explicitly.
Name-collision cascade. Two packages registering dashboard.index produce a last-writer-wins result with no error. Namespacing route names is the only defense.
Extension point (bundled version). If you're forced to vendor the bundle, isolate it behind a thin adapter with a single registerRoutes(Bundle $bundle, string $resource) interface. That lets you swap in a native implementation later without touching call sites.
Extension point (Laravel). Wrap loadRoutesFrom() in a small helper in your shared package kernel so middleware/prefix/name defaults are enforced uniformly across packages. Centralizing that policy is the closest you'll get to a compiler pass.
Symfony 4+ users: the bundle is redundant. Flex recipes inject routing imports on bundle install, and PHP route configurators plus attribute routing removed most of the manual-edit pain the bundle was built for. Do not add it to a modern Symfony app.
If the requesting team says "Laravel": stop. Not applicable, not installable, no gap exists — Laravel's service-provider loadRoutesFrom() already solves this. The correct deliverable is the one-day spike described above, not a dependency review.
How can I help you explore Laravel packages today?