February 15, 2018 Javier Eguiluz

Symfony 4 is the fastest PHP framework according to independent benchmarks, but we are continuously working on making it faster. In Symfony 4.1, we improved the Routing component to make it much faster when matching incoming URLs.

In web applications, routing is divided in two main operations: generation, which generates a URL from the given route and parameters; and matching, which decides which PHP code (i.e. controller) is executed as the response of an incoming URL.

In order to speed up the application, during the compilation phase Symfony generates a PHP class called "matcher" which contains all the route definitions optimized to match incoming URLs. For example, this is a snippet of the class generated for the Symfony Demo application:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 // var/cache/prod/srcProdDebugProjectContainerUrlMatcher.php class srcProdDebugProjectContainerUrlMatcher { // ... public function match ( $rawPathinfo ) { // ... // blog_post if ( preg_match ( '#^/(?P<_locale>en|fr|de|es)/blog/posts/(?P<slug>[^/]++)$#s' , $pathinfo , $matches )) { if ( 'GET' !== $canonicalMethod ) { $allow [] = 'GET' ; goto not_blog_post ; } return $this -> mergeDefaults ( array_replace ( $matches , array ( '_route' => 'blog_post' )), array ( '_controller' => 'App\\Controller\\BlogController::postShow' , '_locale' => 'en' ,)); } not_blog_post : // ... } }

In Symfony 4.1 we refactored the matcher class generator based on the ideas shared in the following article: Fast request routing using regular expressions. The article explains the technique used by FastRoute, a routing library created by the genius PHP contributor Nikita Popov.

The basic idea is to avoid making separate preg_match() calls for each route and instead, combine all regular expressions into a single regular expression. We also made many other big and small optimizations. If you are curious, see the Pull Request #26059 and #26169 for all the details.

All in all, using the same Symfony Demo application example, this is how the blog_post route is matched now:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 $regexList = array ( 0 => '{^(?' . '|/(en|fr|de|es)/admin/post/?(*:82)' . '|/(en|fr|de|es)/admin/post/new(*:166)' . '|/(en|fr|de|es)/admin/post/(\\d+)(*:253)' . '|/(en|fr|de|es)/admin/post/(\\d+)/edit(*:345)' . '|/(en|fr|de|es)/admin/post/([^/]++)/delete(*:442)' . '|/(en|fr|de|es)/blog/?(*:519)' . '|/(en|fr|de|es)/blog/rss\\.xml(*:603)' . '|/(en|fr|de|es)/blog/page/([1-9]\\d*)(*:694)' . '|/(en|fr|de|es)/blog/posts/([^/]++)(*:784)' . '|/(en|fr|de|es)/blog/comment/([^/]++)/new(*:880)' . '|/(en|fr|de|es)/blog/search(*:962)' . '|/(en|fr|de|es)/login(*:1038)' . '|/(en|fr|de|es)/logout(*:1116)' . '|/(en|fr|de|es)?(*:1188)' . ')$}sD' , ); foreach ( $regexList as $offset => $regex ) { // ... default : $routes = array ( // ... 784 => array ( array ( '_route' => 'blog_post' , '_controller' => 'App\\Controller\\BlogController::postShow' , '_locale' => 'en' ), array ( '_locale' , 'slug' ), array ( 'GET' => 0 ), null ), ); // ... }

In practice, combining all regular expressions improves URL matching performance by almost two orders of magnitude. In our benchmarks, Symfony 4.1 URL matching is 77 times faster than in previous Symfony versions. This also means that Symfony 4.1 router is now the fastest PHP router, beating FastRoute and all the other routing libraries.

Best of all: you don't need to make any change in your application to use this fast router. You just need to upgrade to Symfony 4.1 when it's released at the end of May 2018. Meanwhile you can test it in your applications and report any issue that you find.

Update: Nicolas has published two technical articles explaining in detail how the new router works: