Symfony Routing
Under the hood
Confoo Montreal,
February 24th, 2022
© David Buchmann
David Buchmann - david@liip.ch
PHP Engineer, Liip AG, Switzerland
var
`-- www
`-- htdocs
|-- index.htm
|-- about.htm
`-- news.htm
What about...
- application architecture?
- consistent URL schema?
- SEO?
- generating links?
Front controller
RewriteRule ^(.*)$ app.php [QSA,L]
Routing
What PHP code to call for an URL?
switch($path) {
case '/':
return $controller->renderHomepage();
case '/about':
return $controller->renderAbout();
default:
return $controller->render404();
}
What about /product/{id} ?
Regular Expressions!
What about flexibility and efficiency?
...
Use an existing library!
The Symfony Routing component
- Transform
Request
object to hashmap with routing information
- Full stack framework: _controller
- Match on URL, hostname or other headers
- Order of route definition matters: First match wins
public interface RequestMatcherInterface
{
/**
* @return array<string, string>
*/
public function matchRequest(
Request $request
): array;
}
The default router
- RouteCollection and ways to define it
- UrlMatcher and UrlGenerator
- Router (all in one)
- LoaderInterface
- Matcher and generator
- Caching functionality
Define routes
- In annotations on the controller
- In configuration files (YAML, XML)
- PHP code calling a configuration API
- Routing is compiled to PHP code
Caching
- During development: Checks for file changes
- Production: Only on cache:clear
Optimizations
- Compile routes to PHP
- Dump matcher, single class
- Group similar routes
- Prefer strpos, use regex only when needed
- Possesive quantifiers in regex
Optimizations: Group routes
/foo/x/{id}
/foo/y/{id}
/bar/baz
/foo
+---/x/
| `--{id}
`---/y/
`--{id}
/bar/baz
Tweaks in your application
- Most frequently hit routes first
- Order matters: Similar paths consecutive
Providing Context
$context = $this->router->getContext();
$context->setHost('example.com');
$context->setScheme('https');
$context->setBaseUrl('my/path');
$this->router->generate(...);
Symfony documentation
Custom router implementation
- Router used in every single request
- Careful with side effects
- Look at the ChainRouter
Routing in the Symfony Framework
Custom loader: FOSRestBundle
# app/config/routing.yml
product:
type: rest
resource: AppBundle\Controller\ProductController
- Reflection on controller class
- Naming conventions
- Annotations to tweak things
- No need to define routes explicitly
Custom Routers: Multilanguage
- Different URL per language
- Detect locale
- Generate URL in requested locale
- Avoid duplication of configuration
JMSI18nRoutingBundle
- Translate pattern in Symfony translation
- Path prefix for locale or domain per locale
# routing.yml
homepage:
path: /welcome
defaults: { _controller: MyWebsiteBundle:Frontend:index }
# translations/routes.de.yml
homepage: /willkommen
BeSimpleI18nRoutingBundle
- Custom type of route when loading
- Configure path in localized fields
type: be_simple_i18n
...
homepage:
locales:
en: /welcome
de: /willkommen
defaults: { _controller: MyWebsiteBundle:Frontend:index }
CMF DynamicRouter
- Load candidate routes from database
- Indexed lookup
- Handle huge number of routes with no performance penalty
- No issue with frequently editing routes
- SEO URLs for content pages
CMF ChainRouter
Combine several routers
- Cycle through all routers
- Symfony default and your own or the DynamicRouter
Look Beyond the Horizon
Interact with the route matching
- Routing happens during
kernel.request
at priority 32
- Add event listeners with a lower priority number
- If you set a Response on the GetResponseEvent, normal routing will not be triggered
FOSHttpCacheBundle hash listener
function onKernelRequest(GetResponseEvent $event) {
$accept = $event->getRequest()
->headers->get('accept');
if ('user-context-hash' !== $accept) return;
$hash = $this->hashGenerator->generateHash();
$response = new Response('', 200, array(
$this->hashHeader => $hash,
'Content-Type' => 'user-context-hash',
));
$event->setResponse($response);
}
LiipThemeBundle:
Theme specific controllers
my_route:
path: /welcome
defaults:
_controller: my_service:fooAction
theme_controllers:
a: my_other_service:fooAction
b: App:Other:foo
function onKernelRequest(GetResponseEvent $event) {
$request = $event->getRequest();
$theme = $this->activeTheme->getName();
$controllers = $request->attributes
->get('theme_controllers');
if (isset($controllers[$theme])) {
$request->attributes->set(
'_controller',
$controllers[$theme]
);
}
}
Upcast controller arguments with ParamConverter
https://symfony.com/doc/current/components/http_kernel.html
ParamConverter
- Convert string (id, slug, ...) to object
- Based on controller method signature
- ORM and DateTime converter provided
- Annotations to tweak behaviour
- Write your own
- Triggered during the
kernel.controller
event
Questions / Input / Feedback ?
Twitter: @dbu