symfony/routing
The Symfony Routing component maps an HTTP request to a set of configuration variables. It matches incoming URLs to routes and generates URLs from route names, supporting parameters, requirements, defaults, HTTP methods, and localized paths. Install it in any PHP project via Composer.
symfony/routing is already pulled in transitively by laravel/framework, so check what you have before adding a constraint:
composer show symfony/routing
# only add an explicit constraint if you need a specific patched version
composer require symfony/routing:^5.4.54
Version note: the current LTS patch is v5.4.54, a security release that also fixes matching of the
"0"URL (bug #65728). If you call Symfony's matcher directly (rather than only going through Laravel's router), bump to at least5.4.54. The8.1.0-BETA3line is the forward branch — treat it as unstable for production and pin the 5.4 LTS if you need the guarantees.
Laravel already abstracts routing, but you can use Symfony's routing directly for custom logic:
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\Matcher\UrlMatcher;
use Symfony\Component\Routing\Generator\UrlGenerator;
use Symfony\Component\Routing\RequestContext;
// Define routes
$routes = new RouteCollection();
$routes->add('blog_show', new Route('/blog/{slug}', [
'_controller' => 'App\Http\Controllers\BlogController@show',
]));
// Match a request
$context = new RequestContext();
$matcher = new UrlMatcher($routes, $context);
$params = $matcher->match('/blog/hello-world');
// Generate a URL
$generator = new UrlGenerator($routes, $context);
$url = $generator->generate('blog_show', ['slug' => 'hello-world']);
Illuminate\Routing\Router for Laravel-specific routing, but Symfony's routing is useful for:
routes/*.php (DB/API-driven)Define routes programmatically or via YAML/JSON (Symfony's format, but adaptable to Laravel):
// Programmatic route definition
$route = new Route('/user/{id}', [
'_controller' => 'App\Http\Controllers\UserController@show',
], [
'id' => '\d+', // requirements
]);
$route->setMethods(['GET', 'HEAD']);
$routes->add('user_show', $route);
Note the argument order: new Route($path, $defaults, $requirements, $options, $host, $schemes, $methods, $condition). Methods belong on the route, not in defaults.
Use RequestContext to manage global routing parameters (base path, scheme, host, HTTP method):
$context = new RequestContext();
$context->setBaseUrl('/app'); // path prefix only, not a full URL
$context->setScheme('https');
$context->setHost('example.com');
$context->setMethod('GET');
In Laravel, derive it from the incoming request instead of hardcoding:
use Symfony\Component\Routing\RequestContext;
$context = (new RequestContext())->fromRequest($request);
Anything not consumed by the path becomes the query string — pass it in the parameters array:
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
$url = $generator->generate('blog_show', [
'slug' => 'post-1',
'page' => 2,
'sort' => 'desc',
], UrlGeneratorInterface::ABSOLUTE_URL);
// https://example.com/blog/post-1?page=2&sort=desc
generate() takes exactly three arguments (name, parameters, referenceType); there is no separate "extra query" argument.
Match routes while respecting HTTP methods:
$matcher = new UrlMatcher($routes, $context);
$params = $matcher->matchRequest($request); // uses $request->getMethod() and host/scheme
Catch MethodNotAllowedException and use getAllowedMethods() to build a 405 with an Allow header.
Bind the interfaces in a service provider and inject them:
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
class BlogController extends Controller
{
public function show(UrlGeneratorInterface $urlGenerator)
{
$url = $urlGenerator->generate('blog_show', ['slug' => 'test']);
return view('blog.show')->with('url', $url);
}
}
// AppServiceProvider::register()
$this->app->singleton(UrlGeneratorInterface::class, function ($app) {
return new \Symfony\Component\Routing\Generator\UrlGenerator(
$app['router']->getRoutes(), // Laravel's RouteCollection, not Symfony's
$app['request_context']
);
});
Only do this if you genuinely need Symfony's generator — Laravel's route()/URL::to() covers most cases and stays consistent with middleware and signed URLs.
Load routes from external sources (e.g. API responses, database, tenant config):
$routes = new RouteCollection();
foreach ($apiRoutes as $name => $routeData) {
$routes->add($name, new Route($routeData['path'], $routeData['defaults'] ?? []));
}
Build this once per boot and cache it — rebuilding on every request is the most common performance mistake here.
Define constraints for route parameters:
$route = new Route('/post/{year}/{month}/{day}', [], [
'year' => '\d{4}',
'month' => '(0[1-9]|1[0-2])',
'day' => '(0[1-9]|[12][0-9]|3[01])',
]);
The "0" path/parameter bug (fixed in 5.4.54)
Values that are the string "0" were falsy-checked in the matcher, so a route like /0 or a parameter literally equal to 0 could fail to match or be dropped. If you are pinned below 5.4.54, upgrade or avoid bare 0 segments.
Security release
5.4.54 is flagged [SECURITY]. Treat this as a mandatory patch if any part of your stack calls Symfony's matcher/generator directly, and run composer audit after updating.
Case Sensitivity in Host Matching
Host-based routes are case-sensitive by default. Normalize with strtolower() (or RequestContext::setHost(strtolower($host))) if you need case-insensitive matching.
Query Parameter Handling
Route parameters and query parameters share one array. A key that matches a path placeholder is consumed; everything else is appended to the query string. Numeric keys (?0=value) get renumbered — prefer associative keys.
URL Generation with Absolute Paths
ABSOLUTE_URL requires a configured RequestContext (scheme, host, baseUrl); otherwise you get scheme-relative or path-only output. Use fromRequest() in Laravel rather than hand-setting values.
Route Overrides Adding a route with an existing name silently replaces it. Check first:
if ($routes->get($name) !== null) { /* prefix or skip */ }
Circular References in Route Collections
Avoid nesting a collection inside itself. Use RouteCollection::addCollection() for genuine nesting, and be careful with addPrefix() applied to an already-prefixed collection.
Dump Route Parameters
try {
$params = $matcher->match('/path');
dump($params);
} catch (\Symfony\Component\Routing\Exception\ResourceNotFoundException $e) {
dump('no match');
}
Check Route Requirements
If a route isn't matching, the regex in requirements or a defaults value is the usual culprit — especially anchors (^...$ are added for you) and unescaped slashes.
Enable Route Debugging in Laravel
php artisan route:list
Or dump the collection itself:
dd(iterator_to_array($routes->getIterator()));
Inspect $route->compile()->getRegex() to see exactly what the matcher is testing.
Request Context Persistence
RequestContext is mutable and not request-isolated. Recreate it per request or bind it as a scoped singleton — sharing it across requests in a queue worker or Octane will leak host/scheme between requests.
Host and Scheme Defaults Unconfigured context omits scheme and host from generated URLs. Set them explicitly, or derive from the request.
Route Name Conflicts
Laravel and Symfony route names live in different collections but can collide conceptually. Prefix Symfony-registered names (symfony_blog_show) to keep cache keys and debugging unambiguous.
Compiled Route Cache Invalidation
Cached/compiled routes key off the URL and requirements; changing a requirement without clearing the cache yields stale matches. Clear with php artisan route:clear and any custom cache key you introduced.
Custom Route Loaders
Implement Symfony\Component\Config\Loader\LoaderInterface to load routes from a database, tenant config, or remote API; tag it as a routing loader so the router picks it up.
Route Compilation
Use $route->compile() for pre-compiled regexes in performance-critical paths, and cache the resulting collection.
Middleware Integration
Wrap UrlMatcher in middleware to dispatch custom-registered routes before Laravel's router sees them; throw/abort 404 when ResourceNotFoundException is raised.
Attribute-Based Routing Symfony's attribute/annotation loaders let you declare routes on controller methods; wire them through a custom loader if you want that style inside a Laravel app:
use Symfony\Component\Routing\Loader\AttributeClassLoader;
// subclass AttributeClassLoader and register it with a Config\Loader\LoaderResolver
Route Caching
Cache the built RouteCollection (serialize or dump to a compiled PHP array) and rebuild only when definitions change — never per request.
How can I help you explore Laravel packages today?