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

Conditional Routing Bundle Laravel Package

piotrpolak/conditional-routing-bundle

Symfony bundle that adds conditional routing, letting you define routes that match only when configured conditions are met. Useful for feature flags, environment-specific endpoints, and request-aware route selection, with simple routing configuration and integration with Symfony's routing component.

View on GitHub
Deep Wiki
Context7

Getting Started

  • Confirm the target framework first. This is a Symfony bundle, not a Laravel package. There is no service provider, no php artisan vendor:publish, no facade, no config file in config/. If you are in a Laravel codebase, you are reading this as a design reference (see the Laravel equivalents below), not as a dependency — Composer may install it, but nothing in Laravel will ever load it.
  • Check composer.json before anything else. Last release was 2018‑10‑25, so expect PHP 7.x-era constraints and symfony/framework-bundle / symfony/routing constraints in the 2.8–4.x range. Never force it with --ignore-platform-reqs in a real app; pin ^1.0 if you do adopt it.
  • Register the bundle. Symfony 3/4: $bundles[] = new PiotrPolak\ConditionalRoutingBundle\...(); in AppKernel.php. Symfony 4+/5+: entry in config/bundles.php. Note the 1.0.0 rename Piotrpolak → PiotrPolak — use the new case everywhere.
  • Where to look first. The README is thin; the bundle is the documentation. Read in this order:
    1. DependencyInjection/*Extension.php — the real config schema (which condition keys exist).
    2. Routing/ — the loader/decorator that decides which route collections get loaded. This is the whole feature.
    3. Resources/config/services.yml — service ids and any tags used to collect conditions.
  • First use case (the one this bundle exists for): stop writing if ($this->getEnvironment() === 'dev') { ... } around routing.yml imports. Instead, declare a per-bundle (or per-collection) condition — e.g. environment is dev, host matches *.internal, a feature-flag service returns true — and let the loader skip those routes automatically.
  • Verify it works in both states. Run bin/console debug:router and bin/console router:match /some/gated/path with the condition satisfied and unsatisfied. If the route list is identical in both runs, your condition is not being wired (usually a missing service tag or a stale cache).

Implementation Patterns

  • Write conditions as small, pure services. One class per predicate, implementing the bundle's condition contract (check src/ for the exact interface name and method signature). It should read the Request and container-provided values and return a boolean — no DB writes, no session mutation, no HTTP calls.
  • Compose, don't build god-conditions. The maintainable shape is IsDevEnvironment, MatchesInternalHost, FeatureFlagEnabled('new-admin'), each registered as a service, with AND/OR applied in config or a thin composite condition. This keeps each predicate unit-testable.
  • Use it for route existence, not authorization. Gating a route collection off means the route does not exist for that request. That's the right tool for “this debug/admin/reporting bundle shouldn't be routed in production” and the wrong tool for “this user may not access this page” (use Symfony's access_control/voters for that).
  • Keep gated route sets isolated per bundle. The pattern plays nicest when each third-party or optional bundle owns its own route file; you then gate at the bundle/collection boundary rather than cherry-picking individual routes, which keeps debug:router output comprehensible.
  • Guard URL generation. Anything generated from a gated route ($urlGenerator->generate('admin_...'), Twig path(), redirects, sitemap builders, mailers) will throw RouteNotFoundException in the environment where it's disabled. Wrap generation in $router->getRouteCollection()->get(...) checks or a helper, and add a smoke test that renders your layout in the "conditions off" environment.
  • Test conditions in isolation and integration. Unit-test each condition class with a mocked request. Then do functional tests with different HTTP_HOST/server vars and APP_ENV values, asserting the route is 404 vs 200 — that catches loader and cache-layer regressions the unit test cannot.
  • Laravel equivalents (what a Laravel dev should actually build). If you want this behaviour in Laravel, it's ~50 lines in RouteServiceProvider::boot():
    • Conditional registration: if (app()->environment('local') || config('features.admin')) { Route::middleware('web')->group(base_path('routes/admin.php')); } — this is the exact analogue of “skip loading the collection”.
    • Request-context gating: Route::domain('{tenant}.app.test')->group(...) plus route-level middleware for host/header predicates.
    • Per-request gating of an already-registered route: middleware returning 404/403 — note this is different semantics (route exists, request refused) versus the bundle's "route never registered".
    • Encapsulate predicates in a RouteCondition interface with passes(Request $request): bool and a registrar that loops through them before ->group(); that reproduces the bundle's extensibility without the Symfony coupling.
  • If you're porting a Symfony app to Laravel (or running both), map: bundle registration → service provider; DI extension config tree → config/*.php + mergeConfigFrom; loader decorator → RouteServiceProvider conditional blocks; service tags → container tags/app()->tagged().

Gotchas and Tips

  • The namespace rename is the migration. 1.0.0 changed Piotrpolak → PiotrPolak. Update use statements, config/bundles.php, AppKernel.php, and — easy to miss — YAML/XML service definitions where the class is written as a string (class: PiotrPolak\..., arguments:). PSR-4 path mapping is case-sensitive on Linux/macOS, so one stale reference is a hard Class not found.
  • Clear caches after every condition or config change. bin/console cache:clear (and rm -rf var/cache/* if the compiled container is wedged). A stale container will happily keep the old condition wiring and make you debug the wrong file for an hour.
  • Route caching is the #1 "works in dev, fails in prod" trap. Conditions evaluated at route-load time are baked into the compiled/cached router. If a condition depends on runtime request data (host, header, user), verify how the loader behaves under a warmed cache — anything request-dependent really belongs in a firewall/voter or middleware, not the loader.
  • Conditions run on the routing hot path. They execute on every request (and during warmup). Cache static inputs (env, config flags) but never add per-request IO — no DB lookups, no Redis hits, no HTTP calls — or you've added latency to every page including 404s.
  • Timing: not everything exists yet at load time. Security token, session, and firewall context may be unavailable or incomplete when the router loads collections. Conditions keyed off "is the current user an admin" are fragile — prefer middleware/voter gating for that.
  • Semantics: disabled route = 404, not 403. Users hitting a gated-off URL get "no route found". If product expects "forbidden", this bundle is the wrong layer. Document this before someone files a security ticket.
  • Treat it as unmaintained. 5 stars, one release note, no changelog, no upgrade guide, no documented supported PHP/Symfony matrix. Before depending on it, decide whether you'd rather vendor a copy of the loader into your own bundle — at this size, forking is often cheaper than owning an abandoned dependency.
  • Debugging checklist, in order: (1) debug:container --tag=<condition tag> to confirm your condition service is registered and tagged; (2) debug:router in both condition states; (3) var/log/dev.log for loader exceptions swallowed into a generic routing error; (4) cache:clear; (5) confirm the bundle directory on disk is src/PiotrPolak/, not src/Piotrpolak/.
  • Extension points and their cost. You can add conditions, override the loader service, or replace the DI extension config — but each override is an upgrade hazard against a package that isn't releasing. Keep the surface small and re-run your dual-environment route tests after any dependency bump.
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.
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core