Symfony Routing
Under the hood
Symfony Con, Paris,
December 3rd, 2015
© David Buchmann
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
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 array with routing information
- Full stack framework: _controller
public interface RequestMatcherInterface
{
/**
* @param Request $request request to match
*
* @return array An array of parameters
*
* @throws ResourceNotFoundException
* @throws MethodNotAllowedException
*/
public function matchRequest(Request $request);
}
The default router
- RouteCollection and ways to define it
- UrlMatcher and UrlGenerator
- Router (all in one)
- LoaderInterface
- Matcher and generator
- Caching functionality
What is the fastest routing?
YML, XML, PHP or Annotations?
Correct: It does not matter!
Routing is compiled to PHP code
Caching
- dev env checks for file changes
- prod env only on cache:clear
Optimizations
- Compile routes to PHP
- Dump cached 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
Lets go full stack!
Not only configuration files
- Load routes in php files for flexibility
- Custom route loaders
- Since 2.8: Service as route resource:
# app/config/routing.yml
admin_routes:
type: service
resource: "admin_route_loader:loadRoutes"
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 }
Editable routes with 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
ChainRouter
Combine several routers
- Cycle through all routers
- Symfony default and your own or the DynamicRouter
Look Beyond the Horizon
Beyond Request to Controller mapping
- Routing happens during
kernel.request
at priority 32
- Add event listeners with a lower priority
- Upcast controller arguments with
ParamConverter
http://symfony.com/doc/current/components/http_kernel/introduction.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
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]
);
}
}
ThormeierBreadcrumbBundle
acme_demo_home:
path: /
options:
breadcrumb:
label: Home
acme_demo_contact:
path: /contact
options:
breadcrumb:
label: Contact
parent_route: acme_demo_home
Questions / Input / Feedback ?
Twitter: @dbu