├── .gitignore ├── .travis.yml ├── LICENSE.txt ├── README.md ├── RouteGenerator.php ├── adapters ├── aura.php ├── cakephp.php ├── fast_route.php ├── fast_route_classic.php ├── laravel.php ├── li3.php ├── phroute.php ├── router.php └── symfony.php ├── benchmark.php ├── benchmarks ├── aura │ ├── path.php │ └── subdomain.php ├── cakephp │ ├── path.php │ └── routes.php ├── fast_route │ └── path.php ├── fast_route_classic │ └── path.php ├── laravel │ ├── path.php │ └── subdomain.php ├── li3 │ ├── path.php │ └── subdomain.php ├── phroute │ └── path.php ├── router │ ├── path.php │ └── subdomain.php └── symfony │ ├── path.php │ └── subdomain.php └── composer.json /.gitignore: -------------------------------------------------------------------------------- 1 | composer.lock 2 | composer.phar 3 | vendor 4 | libraries 5 | bin 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: php 3 | 4 | php: 5 | - 7.1 6 | - 7.2 7 | 8 | 9 | matrix: 10 | allow_failures: 11 | - php: hhvm 12 | fast_finish: true 13 | 14 | before_script: 15 | - composer config -g github-oauth.github.com $GITHUB_COMPOSER_AUTH 16 | - composer install 17 | 18 | script: ./benchmark.php 19 | 20 | env: 21 | global: 22 | secure: WlJqd0v1GrwCTlQQ5Bb8xk0qDVuagIDtciWl6+Ewdps41Pdnns+Trdg/6doyujn05aAxFMffW9VquejUtB1c3tghxxvPO2/6g/kKFYjsczXGwe5LgqJxtkwfW1KRl3K3U3koblZ76nTZ3ENZogCNOhY41MBXRD5SnMMCk8IeIsB3g4M5qRypkA4zi7/fgFF8L78gzhR99isiOSXqJGjYi/LuQGz535paOgoQprpp3Rfed9P8uiXpSvK8OFCTK7Td9DH94uk7LSyLvNwUDd8acxQU5F09nEKlnrdEfDEzs5KQi1MvGQ3+kr+gqlq+EVMCEirwTfOAqfJbQv+oyT4Z3CffyohKUoO8RIYV5uLf+J8VC02qd1afYaA7eBfHjsi4eVO1Y/91RgDRnHYbVKYClKXszPRJT8v+/26VDPNxdXQCVPRirjpASzwUrP9d0/SmYod9xohjGRtvMVGgfb2ymDuWIcWn6BFPaWrnftsTsjmpTFNAyWl6aUcfeO7Nq1AJdRY/IF2FMB0BG4kn0Woft3gSpXrY2AkI28CjMTp0DHr5WAZeE5xFznfZz6w3dcPU+N0q+E2ukLIVDcDGdfF/GOV2neS2xZ+JkMdZ15uVRjQUplZHQLwDIh+PHV39mnKw/PobKHscEJUFFBsTm26WJywxUqiC2mESusoZ20gg1A4= 23 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 CrysaLEAD 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PHP Router Benchmark 2 | 3 | [![Build Status](https://travis-ci.org/jails/php-router-benchmark.svg?branch=master)](https://travis-ci.org/jails/php-router-benchmark) 4 | 5 | The intent here is to benchmark and also inventory all popular PHP routing solutions around. 6 | 7 | ## Requirements 8 | 9 | php = 7.3 10 | 11 | ## Installation 12 | 13 | Clone the repo then: 14 | 15 | ```bash 16 | composer install 17 | ./benchmark.php 18 | ``` 19 | 20 | ## Benchmarking process 21 | 22 | The current test creates 100 unique routes with 3 variables placeholder each. 23 | 24 | Example of route: `/controller1/action1/{id}/{arg1}/{arg2}` 25 | 26 | This benchmarking will be run on the following three different situations for both path & subdomain: 27 | * the best case (i.e when a request matches the first route for all differents HTTP method) 28 | * the worst case (i.e when a request matches the last route for all differents HTTP method) 29 | * the average case (i.e the mean which is probably the most realistic test). 30 | 31 | And all tests will be run using the following sets of routes: 32 | * in the first set all routes matches all HTTP methods. 33 | * in the second set all routes matches only a single HTTP method. 34 | 35 | The benchmarked routing implementations are: 36 | 37 | * [Router](https://github.com/crysalead/router) 38 | * [Li3](https://github.com/UnionOfRAD/lithium) 39 | * [FastRoute](https://github.com/nikic/FastRoute) 40 | * [FastRoute\*](https://github.com/jails/FastRoute) = FastRoute + [a classic routing strategy](https://github.com/jails/FastRoute/commit/114676515b636b637f6cac53945c2e04875b60eb) 41 | * [Symfony](https://github.com/symfony/routing) 42 | * [Aura3](https://github.com/auraphp/Aura.Router) 43 | * [PHRoute](https://github.com/mrjgreen/phroute) 44 | 45 | ## Results 46 | 47 | ``` 48 | ################### With Routes Supporting All HTTP Methods ################### 49 | 50 | 51 | 52 | =============================== Best Case (path) =============================== 53 | 54 | Symfony 100% | ████████████████████████████████████████████████████████████ | 55 | Aura3 79% | ███████████████████████████████████████████████ | 56 | Router 73% | ███████████████████████████████████████████ | 57 | Laravel 23% | █████████████ | 58 | FastRoute 12% | ███████ | 59 | FastRoute* 12% | ███████ | 60 | PHRoute 8% | ████ | 61 | Li3 5% | ███ | 62 | 63 | 64 | ============================= Average Case (path) ============================= 65 | 66 | Symfony 100% | ████████████████████████████████████████████████████████████ | 67 | Router 85% | ██████████████████████████████████████████████████ | 68 | Aura3 36% | █████████████████████ | 69 | FastRoute* 22% | █████████████ | 70 | FastRoute 21% | ████████████ | 71 | Laravel 16% | █████████ | 72 | PHRoute 15% | ████████ | 73 | Li3 10% | █████ | 74 | 75 | 76 | ============================== Worst Case (path) ============================== 77 | 78 | Symfony 100% | ████████████████████████████████████████████████████████████ | 79 | Router 77% | █████████████████████████████████████████████ | 80 | FastRoute* 34% | ████████████████████ | 81 | FastRoute 32% | ███████████████████ | 82 | Aura3 30% | ██████████████████ | 83 | PHRoute 23% | █████████████ | 84 | Laravel 14% | ████████ | 85 | Li3 13% | ███████ | 86 | 87 | 88 | ============================ Best Case (sub-domain) ============================ 89 | 90 | Router 100% | ████████████████████████████████████████████████████████████ | 91 | Symfony 50% | ██████████████████████████████ | 92 | Aura3 11% | ██████ | 93 | Li3 5% | ███ | 94 | Laravel 2% | █ | 95 | 96 | 97 | ========================== Average Case (sub-domain) ========================== 98 | 99 | Router 100% | ████████████████████████████████████████████████████████████ | 100 | Symfony 78% | ██████████████████████████████████████████████ | 101 | Aura3 15% | ████████ | 102 | Li3 9% | █████ | 103 | Laravel 3% | █ | 104 | 105 | 106 | =========================== Worst Case (sub-domain) =========================== 107 | 108 | Symfony 100% | ████████████████████████████████████████████████████████████ | 109 | Router 92% | ███████████████████████████████████████████████████████ | 110 | Aura3 17% | ██████████ | 111 | Li3 11% | ██████ | 112 | Laravel 4% | ██ | 113 | 114 | 115 | ############## With Routes Supporting Only A Single HTTP Methods ############## 116 | 117 | 118 | 119 | =============================== Best Case (path) =============================== 120 | 121 | Aura3 100% | ████████████████████████████████████████████████████████████ | 122 | Symfony 97% | █████████████████████████████████████████████████████████ | 123 | Router 81% | ████████████████████████████████████████████████ | 124 | FastRoute* 51% | ██████████████████████████████ | 125 | FastRoute 46% | ███████████████████████████ | 126 | Laravel 39% | ███████████████████████ | 127 | PHRoute 31% | ██████████████████ | 128 | Li3 24% | ██████████████ | 129 | 130 | 131 | ============================= Average Case (path) ============================= 132 | 133 | FastRoute* 100% | ████████████████████████████████████████████████████████████ | 134 | Router 95% | █████████████████████████████████████████████████████████ | 135 | FastRoute 93% | ████████████████████████████████████████████████████████ | 136 | Symfony 89% | █████████████████████████████████████████████████████ | 137 | PHRoute 62% | █████████████████████████████████████ | 138 | Laravel 41% | ████████████████████████ | 139 | Aura3 33% | ███████████████████ | 140 | Li3 31% | ██████████████████ | 141 | 142 | 143 | ============================== Worst Case (path) ============================== 144 | 145 | FastRoute* 100% | ████████████████████████████████████████████████████████████ | 146 | FastRoute 94% | ████████████████████████████████████████████████████████ | 147 | PHRoute 62% | ████████████████████████████████████ | 148 | Symfony 61% | ████████████████████████████████████ | 149 | Router 56% | █████████████████████████████████ | 150 | Laravel 28% | ████████████████ | 151 | Li3 24% | ██████████████ | 152 | Aura3 19% | ███████████ | 153 | 154 | 155 | ============================ Best Case (sub-domain) ============================ 156 | 157 | Router 100% | ████████████████████████████████████████████████████████████ | 158 | Symfony 55% | █████████████████████████████████ | 159 | Aura3 12% | ███████ | 160 | Li3 12% | ██████ | 161 | Laravel 8% | ████ | 162 | 163 | 164 | ========================== Average Case (sub-domain) ========================== 165 | 166 | Router 100% | ████████████████████████████████████████████████████████████ | 167 | Symfony 80% | ████████████████████████████████████████████████ | 168 | Li3 17% | ██████████ | 169 | Aura3 15% | █████████ | 170 | Laravel 11% | ██████ | 171 | 172 | 173 | =========================== Worst Case (sub-domain) =========================== 174 | 175 | Router 100% | ████████████████████████████████████████████████████████████ | 176 | Symfony 79% | ███████████████████████████████████████████████ | 177 | Li3 16% | █████████ | 178 | Aura3 15% | █████████ | 179 | Laravel 11% | ██████ | 180 | ``` 181 | -------------------------------------------------------------------------------- /RouteGenerator.php: -------------------------------------------------------------------------------- 1 | _parser = new Parser(); 72 | } 73 | 74 | public function methods() 75 | { 76 | return $this->_methods; 77 | } 78 | 79 | public function hosts() 80 | { 81 | return $this->_hosts; 82 | } 83 | 84 | public function host($host, $scheme = '*') 85 | { 86 | $this->_scheme = '*'; 87 | $this->_host = $this->_parser->tokenize($host, '/'); 88 | } 89 | 90 | public function template($template, $constraints = []) 91 | { 92 | $this->_template = $this->_parser->tokenize($template, '/'); 93 | $this->_constraints = $constraints; 94 | } 95 | 96 | /** 97 | * Gets/sets the number of routes. 98 | * 99 | * @param integer $nb The number of routes to set or none the get the current one. 100 | * @return integer The number of routes or `$this` on set. 101 | */ 102 | public function nbRoutes($nb = null) 103 | { 104 | if (!func_num_args()) { 105 | return $this->_nbRoutes; 106 | } 107 | $this->_nbRoutes = $nb; 108 | return $this; 109 | } 110 | 111 | /** 112 | * Generates a bunch of routes. 113 | * 114 | * @param callable $placeholderHandler The placeholder formatter handler. 115 | * @param callable $optionalSegmentHandler The optional segment formatter handler. 116 | * @param array $options An option array. 117 | * @return array The generated routes array. 118 | */ 119 | public function generate($placeholderHandler, $optionalSegmentHandler = null, $options = []) 120 | { 121 | $defaults = [ 122 | 'nbHosts' => 1, 123 | 'isolated' => false 124 | ]; 125 | $options += $defaults; 126 | 127 | $isolated = $options['isolated']; 128 | 129 | if (!$this->_template) { 130 | throw new Exception('Missing path template.'); 131 | } 132 | $scheme = $this->_scheme; 133 | 134 | if ($this->_host) { 135 | $segments = $this->_flatten($this->_host['tokens'], $placeholderHandler, $optionalSegmentHandler); 136 | $pattern = join('', $segments); 137 | $nbHosts = $options['nbHosts']; 138 | $hosts = []; 139 | for ($i = 1; $i <= $nbHosts; $i++) { 140 | $host = Text::insert($pattern, ['hostId' => $i], ['before' => '{%', 'after' => '%}']); 141 | $hosts[] = $host; 142 | } 143 | } else { 144 | $hosts = ['*']; 145 | } 146 | 147 | $nbRoutes = $this->nbRoutes(); 148 | $ids = []; 149 | $id = 1; 150 | $constraints = $this->_constraints; 151 | 152 | foreach ($hosts as $host) { 153 | $this->_hosts[] = $host; 154 | $ids[$host] = [ 155 | 'GET' => [], 156 | 'POST' => [], 157 | 'PUT' => [], 158 | 'PATCH' => [], 159 | 'DELETE' => [] 160 | ]; 161 | for ($i = 0; $i < $nbRoutes; $i++) { 162 | $segments = $this->_flatten($this->_template['tokens'], $placeholderHandler, $optionalSegmentHandler); 163 | $pattern = join('', $segments); 164 | $pattern = Text::insert($pattern, ['id' => $id], ['before' => '{%', 'after' => '%}']); 165 | $methods = $isolated ? [$this->_methods[$i % 5]] : $this->_methods; 166 | $name = $id; 167 | $routes[] = compact('name', 'scheme', 'host', 'pattern', 'methods', 'constraints'); 168 | 169 | foreach ($methods as $method) { 170 | $ids[$host][$method][] = $id; 171 | } 172 | 173 | $id++; 174 | } 175 | } 176 | return [$ids, $routes]; 177 | } 178 | 179 | /** 180 | * Returns a list of route segments from a token collection. 181 | */ 182 | protected function _flatten($tokens, $placeholderHandler, $optionalSegmentHandler) 183 | { 184 | $segments = []; 185 | foreach ($tokens as $token) { 186 | if (is_string($token)) { 187 | $segments[] = $token; 188 | } elseif (isset($token['tokens'])) { 189 | $segment = $this->_flatten($token['tokens'], $placeholderHandler, $optionalSegmentHandler); 190 | if (!$optionalSegmentHandler) { 191 | throw new Exception('Missing optional segments handler.'); 192 | } 193 | $segments[] = array_merge($segments, optionalSegmentHandler($segment, $token['greedy'])); 194 | } else { 195 | $segments[] = $placeholderHandler($token['name'], $token['pattern']); 196 | } 197 | } 198 | return $segments; 199 | } 200 | } -------------------------------------------------------------------------------- /adapters/aura.php: -------------------------------------------------------------------------------- 1 | service('build-routes', function() { 8 | 9 | return function($generator, $options = []) { 10 | $placeholderTemplate = function($name, $pattern) { 11 | return "{{$name}}"; 12 | }; 13 | 14 | return $generator->generate($placeholderTemplate, null, $options); 15 | }; 16 | }); 17 | 18 | $box->service('add-routes', function() { 19 | 20 | return function($routes) { 21 | $routerContainer = new RouterContainer(); 22 | $collection = $routerContainer->getMap(); 23 | foreach ($routes as $route) { 24 | $auraRoute = $collection->route($route['name'], $route['pattern'],function() {}); 25 | if ($route['host'] !== '*') { 26 | $auraRoute->host($route['host']); 27 | } 28 | $auraRoute->tokens($route['constraints']); 29 | $auraRoute->allows($route['methods']); 30 | } 31 | return $routerContainer->getMatcher(); 32 | }; 33 | }); 34 | -------------------------------------------------------------------------------- /adapters/cakephp.php: -------------------------------------------------------------------------------- 1 | service('build-routes', function() { 11 | return function($generator, $options = []) { 12 | $placeholderTemplate = function($name, $pattern) { 13 | return ":$name"; 14 | }; 15 | 16 | return $generator->generate($placeholderTemplate, null, $options); 17 | }; 18 | }); 19 | 20 | $box->service('add-routes', function() { 21 | return function($routes) { 22 | Router::setRouteCollection(new \Cake\Routing\RouteCollection()); 23 | 24 | foreach ($routes as $route) { 25 | Router::connect($route['pattern']); 26 | } 27 | }; 28 | }); 29 | -------------------------------------------------------------------------------- /adapters/fast_route.php: -------------------------------------------------------------------------------- 1 | service('build-routes', function() { 7 | 8 | return function($generator, $options = []) { 9 | $placeholderTemplate = function($name, $pattern) { 10 | return "{{$name}:{$pattern}}"; 11 | }; 12 | 13 | $segmentTemplate = function($segment, $greedy) { 14 | return "[{$segment}]" . ($greedy !== '?' ? $greedy : ''); 15 | }; 16 | 17 | return $generator->generate($placeholderTemplate, $segmentTemplate, $options); 18 | }; 19 | }); 20 | 21 | $box->service('add-routes', function() { 22 | 23 | return function($routes) { 24 | $router = FastRoute\simpleDispatcher(function($router) use ($routes) { 25 | foreach ($routes as $route) { 26 | foreach ($route['methods'] as $method) { 27 | $router->addRoute($method, $route['pattern'], function() {}); 28 | } 29 | } 30 | }); 31 | return $router; 32 | }; 33 | }); 34 | -------------------------------------------------------------------------------- /adapters/fast_route_classic.php: -------------------------------------------------------------------------------- 1 | service('build-routes', function() { 7 | 8 | return function($generator, $options = []) { 9 | $placeholderTemplate = function($name, $pattern) { 10 | return "{{$name}:{$pattern}}"; 11 | }; 12 | 13 | $segmentTemplate = function($segment, $greedy) { 14 | return "[{$segment}]" . ($greedy !== '?' ? $greedy : ''); 15 | }; 16 | 17 | return $generator->generate($placeholderTemplate, $segmentTemplate, $options); 18 | }; 19 | }); 20 | 21 | $box->service('add-routes', function() { 22 | 23 | return function($routes) { 24 | $router = FastRoute\classicDispatcher(function($router) use ($routes) { 25 | foreach ($routes as $route) { 26 | foreach ($route['methods'] as $method) { 27 | $router->addRoute($method, $route['pattern'], function() {}); 28 | } 29 | } 30 | }); 31 | return $router; 32 | }; 33 | }); 34 | -------------------------------------------------------------------------------- /adapters/laravel.php: -------------------------------------------------------------------------------- 1 | service('build-routes', function() { 11 | 12 | return function($generator, $options = []) { 13 | $placeholderTemplate = function($name, $pattern) { 14 | return "{{$name}}"; 15 | }; 16 | 17 | return $generator->generate($placeholderTemplate, null, $options); 18 | }; 19 | }); 20 | 21 | $box->service('add-routes', function() { 22 | 23 | return function($routes) { 24 | $dispatcher = new Dispatcher(); 25 | $container = new Container(); 26 | $router = new Router($dispatcher, $container); 27 | 28 | foreach ($routes as $route) { 29 | $action = [ 30 | 'uses' => function($id) { return $id; } 31 | ]; 32 | if ($route['host'] !== "*") { 33 | $action['domain'] = $route['host']; 34 | } 35 | $router->getRoutes()->add(new Route($route['methods'], $route['pattern'], $action))->where($route['constraints']); 36 | } 37 | return $router; 38 | }; 39 | }); 40 | -------------------------------------------------------------------------------- /adapters/li3.php: -------------------------------------------------------------------------------- 1 | service('build-routes', function() { 8 | 9 | return function($generator, $options = []) { 10 | $placeholderTemplate = function($name, $pattern) { 11 | return "{{$name}:{$pattern}}"; 12 | }; 13 | 14 | return $generator->generate($placeholderTemplate, null, $options); 15 | }; 16 | }); 17 | 18 | $box->service('add-routes', function() { 19 | Router::reset(); 20 | return function($routes) { 21 | foreach ($routes as $route) { 22 | foreach ($route['methods'] as $method) { 23 | if ($route['host'] === '*') { 24 | Router::connect($route['pattern'], ['http:method' => $method]); 25 | } else { 26 | Router::attach($route['host'], [ 27 | 'absolute' => true, 28 | 'host' => $route['host'] 29 | ]); 30 | Router::scope($route['host'], function() use ($route, $method) { 31 | Router::connect($route['pattern'], ['http:method' => $method]); 32 | }); 33 | } 34 | } 35 | } 36 | }; 37 | }); 38 | -------------------------------------------------------------------------------- /adapters/phroute.php: -------------------------------------------------------------------------------- 1 | service('build-routes', function() { 9 | 10 | return function($generator, $options = []) { 11 | $placeholderTemplate = function($name, $pattern) { 12 | return "{{$name}:{$pattern}}"; 13 | }; 14 | 15 | $segmentTemplate = function($segment, $greedy) { 16 | return "[{$segment}]" . ($greedy !== '?' ? $greedy : ''); 17 | }; 18 | 19 | return $generator->generate($placeholderTemplate, $segmentTemplate, $options); 20 | }; 21 | }); 22 | 23 | $box->service('add-routes', function() { 24 | 25 | return function($routes) { 26 | $collection = new RouteCollector(); 27 | foreach ($routes as $route) { 28 | foreach ($route['methods'] as $method) { 29 | $collection->addRoute($method, $route['pattern'], function($id) { 30 | return $id; 31 | }); 32 | } 33 | } 34 | return new Dispatcher($collection->getData()); 35 | }; 36 | }); 37 | -------------------------------------------------------------------------------- /adapters/router.php: -------------------------------------------------------------------------------- 1 | service('build-routes', function() { 8 | 9 | return function($generator, $options = []) { 10 | $placeholderTemplate = function($name, $pattern) { 11 | return "{{$name}:{$pattern}}"; 12 | }; 13 | 14 | $segmentTemplate = function($segment, $greedy) { 15 | return "[{$segment}]" . ($greedy !== '?' ? $greedy : ''); 16 | }; 17 | 18 | return $generator->generate($placeholderTemplate, $segmentTemplate, $options); 19 | }; 20 | }); 21 | 22 | $box->service('add-routes', function() { 23 | 24 | return function($routes) { 25 | $router = new Router(); 26 | foreach ($routes as $route) { 27 | $router->bind($route['pattern'], $route, function($route){}); 28 | } 29 | return $router; 30 | }; 31 | }); 32 | -------------------------------------------------------------------------------- /adapters/symfony.php: -------------------------------------------------------------------------------- 1 | service('build-routes', function() { 11 | 12 | return function($generator, $options = []) { 13 | $placeholderTemplate = function($name, $pattern) { 14 | return "{{$name}}"; 15 | }; 16 | 17 | return $generator->generate($placeholderTemplate, null, $options); 18 | }; 19 | }); 20 | 21 | $box->service('add-routes', function() { 22 | 23 | return function($routes) { 24 | $sfCollection = new RouteCollection(); 25 | foreach ($routes as $route) { 26 | $sfRoute = new Route($route['pattern'], ['controller' => 'controller']); 27 | if ($route['host'] !== '*') { 28 | $sfRoute->setHost($route['host']); 29 | } 30 | $sfRoute->setMethods($route['methods']); 31 | $sfRoute->setRequirements($route['constraints']); 32 | $sfCollection->add($route['pattern'], $sfRoute); 33 | } 34 | return new UrlMatcher($sfCollection, new RequestContext()); 35 | }; 36 | }); 37 | -------------------------------------------------------------------------------- /benchmark.php: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | add('lithium\\', __DIR__ . '/libraries'); 7 | 8 | use Lead\Box\Box; 9 | use Lead\Dir\Dir; 10 | use Lead\Benchmark\Benchmark; 11 | 12 | $adapters = Dir::scan('./adapters', [ 13 | 'type' => 'file', 14 | 'skipDots' => true 15 | ]); 16 | 17 | foreach ($adapters as $file) { 18 | if (file_exists($file)) { 19 | include $file; 20 | } 21 | } 22 | 23 | $box = box('benchmark', new Box()); 24 | 25 | $modes = [ 26 | 'With Routes Supporting All HTTP Methods' => false, 27 | 'With Routes Supporting Only A Single HTTP Methods' => true 28 | ]; 29 | 30 | $benchmarks = [ 31 | [ 32 | 'filename' => 'path', 33 | 'nbRoutes' => 100, 34 | 'forms' => [ 35 | 'Best Case (path)' => [ 36 | 'strategy' => function() { 37 | return function(&$ids, &$method, $host = '*') { 38 | return reset($ids[$host][$method]); 39 | }; 40 | } 41 | ], 42 | 'Average Case (path)' => [ 43 | 'strategy' => function() { 44 | return function(&$ids, &$method, $host = '*') { 45 | return $ids[$host][$method][floor(count($ids[$host][$method]) / 2)]; 46 | }; 47 | } 48 | ], 49 | 'Worst Case (path)' => [ 50 | 'strategy' => function() { 51 | return function(&$ids, &$method, $host = '*') { 52 | return end($ids[$host][$method]); 53 | }; 54 | } 55 | ] 56 | ] 57 | ], 58 | [ 59 | 'filename' => 'subdomain', 60 | 'nbRoutes' => 10, 61 | 'forms' => [ 62 | 'Best Case (sub-domain)' => [ 63 | 'nbHosts' => function() { 64 | return 10; 65 | }, 66 | 'strategy' => function() { 67 | return function(&$ids, &$method, $host) { 68 | return reset($ids[$host][$method]); 69 | }; 70 | } 71 | ], 72 | 'Average Case (sub-domain)' => [ 73 | 'nbHosts' => function() { 74 | return 10; 75 | }, 76 | 'strategy' => function() { 77 | return function(&$ids, &$method, $host) { 78 | return $ids[$host][$method][floor(count($ids[$host][$method]) / 2)]; 79 | }; 80 | } 81 | ], 82 | 'Worst Case (sub-domain)' => [ 83 | 'nbHosts' => function() { 84 | return 10; 85 | }, 86 | 'strategy' => function() { 87 | return function(&$ids, &$method, $host) { 88 | return end($ids[$host][$method]); 89 | }; 90 | } 91 | ] 92 | ] 93 | ] 94 | ]; 95 | 96 | $dirs = Dir::scan('./benchmarks', [ 97 | 'type' => 'dir', 98 | 'skipDots' => true 99 | ]); 100 | 101 | foreach ($modes as $title => $isolated) { 102 | echo Benchmark::title($title, '#'); 103 | $box->service('isolated', $isolated); 104 | 105 | foreach ($benchmarks as $bench) { 106 | $name = $bench['filename']; 107 | 108 | foreach ($bench['forms'] as $title => $variables) { 109 | 110 | foreach ($variables as $key => $value) { 111 | $box->service($key, $value); 112 | } 113 | 114 | $benchmark = new Benchmark(); 115 | echo Benchmark::title($title); 116 | $benchmark->repeat(10); 117 | foreach ($dirs as $dir) { 118 | $path = $dir . DIRECTORY_SEPARATOR . $name . ".php"; 119 | 120 | if (file_exists($path)) { 121 | $generator = new RouteGenerator(); 122 | $generator->nbRoutes($bench['nbRoutes']); 123 | include $path; 124 | } 125 | gc_collect_cycles(); 126 | } 127 | echo $benchmark->chart(); 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /benchmarks/aura/path.php: -------------------------------------------------------------------------------- 1 | template('/controller{%id%}/action{%id%}/{id}/{arg1}/{arg2}'); 5 | 6 | $buildRoutes = box('aura')->get('build-routes'); 7 | 8 | list($ids, $routes) = $buildRoutes($generator, [ 9 | 'isolated' => box('benchmark')->get('isolated') 10 | ]); 11 | 12 | $benchmark->run('Aura3', function() use ($box, $generator, $ids, $routes) { 13 | 14 | $addRoutes = box('aura')->get('add-routes'); 15 | $router = $addRoutes($routes); 16 | 17 | $strategy = box('benchmark')->get('strategy'); 18 | 19 | foreach ($generator->methods() as $method) { 20 | $id = $strategy($ids, $method); 21 | 22 | $request = new ServerRequest([], [], "/controller{$id}/action{$id}/{$id}/arg1/arg2", $method); 23 | $route = $router->match($request); 24 | $params = $route->attributes; 25 | 26 | if ($params['id'] !== (string) $id) { 27 | return false; 28 | } 29 | } 30 | 31 | }); 32 | -------------------------------------------------------------------------------- /benchmarks/aura/subdomain.php: -------------------------------------------------------------------------------- 1 | host('subdomain{%hostId%}.domain.com', '*'); 6 | $generator->template('/controller{%id%}/action{%id%}/{id}/{arg1}/{arg2}'); 7 | 8 | $buildRoutes = box('aura')->get('build-routes'); 9 | 10 | list($ids, $routes) = $buildRoutes($generator, [ 11 | 'nbHosts' => box('benchmark')->get('nbHosts'), 12 | 'isolated' => box('benchmark')->get('isolated') 13 | ]); 14 | 15 | $benchmark->run('Aura3', function() use ($box, $generator, $ids, $routes) { 16 | 17 | $addRoutes = box('aura')->get('add-routes'); 18 | $router = $addRoutes($routes); 19 | 20 | $strategy = box('benchmark')->get('strategy'); 21 | 22 | foreach ($generator->hosts() as $host) { 23 | foreach ($generator->methods() as $method) { 24 | $id = $strategy($ids, $method, $host); 25 | 26 | $uri = new Uri("/controller{$id}/action{$id}/{$id}/arg1/arg2"); 27 | $uri = $uri->withHost($host); 28 | $request = new ServerRequest([], [], $uri, $method); 29 | $route = $router->match($request); 30 | $params = $route->attributes; 31 | 32 | if ($params['id'] !== (string) $id) { 33 | return false; 34 | } 35 | } 36 | } 37 | 38 | }); 39 | -------------------------------------------------------------------------------- /benchmarks/cakephp/path.php: -------------------------------------------------------------------------------- 1 | template('/controller{%id%}/action{%id%}/{id}/{arg1}/{arg2}'); 6 | 7 | $buildRoutes = box('CakePHP')->get('build-routes'); 8 | 9 | list($ids, $routes) = $buildRoutes($generator, [ 10 | 'isolated' => box('benchmark')->get('isolated') 11 | ]); 12 | 13 | $benchmark->run('CakePHP', function() use ($box, $generator, $ids, $routes) { 14 | 15 | $addRoutes = box('CakePHP')->get('add-routes'); 16 | $addRoutes($routes); 17 | 18 | $strategy = box('benchmark')->get('strategy'); 19 | 20 | foreach ($generator->methods() as $method) { 21 | $id = $strategy($ids, $method); 22 | 23 | $request = new ServerRequest([], [], "/controller{$id}/action{$id}/{$id}/arg1/arg2", $method); 24 | $result = Router::parseRequest($request); 25 | 26 | if ($result === false) { 27 | return false; 28 | } 29 | } 30 | }); 31 | -------------------------------------------------------------------------------- /benchmarks/cakephp/routes.php: -------------------------------------------------------------------------------- 1 | template('/controller{%id%}/action{%id%}/{id}/{arg1}/{arg2}'); 3 | 4 | $buildRoutes = box('fast-route')->get('build-routes'); 5 | 6 | list($ids, $routes) = $buildRoutes($generator, [ 7 | 'isolated' => box('benchmark')->get('isolated') 8 | ]); 9 | 10 | $benchmark->run('FastRoute', function() use ($box, $generator, $ids, $routes) { 11 | 12 | $addRoutes = box('fast-route')->get('add-routes'); 13 | $router = $addRoutes($routes); 14 | 15 | $strategy = box('benchmark')->get('strategy'); 16 | 17 | foreach ($generator->methods() as $method) { 18 | $id = $strategy($ids, $method); 19 | 20 | $result = $router->dispatch($method, "/controller{$id}/action{$id}/{$id}/arg1/arg2"); 21 | $params = $result[2]; 22 | 23 | if ($params['id'] !== (string) $id) { 24 | return false; 25 | } 26 | } 27 | }); 28 | -------------------------------------------------------------------------------- /benchmarks/fast_route_classic/path.php: -------------------------------------------------------------------------------- 1 | template('/controller{%id%}/action{%id%}/{id}/{arg1}/{arg2}'); 3 | 4 | $buildRoutes = box('fast-route-classic')->get('build-routes'); 5 | 6 | list($ids, $routes) = $buildRoutes($generator, [ 7 | 'isolated' => box('benchmark')->get('isolated') 8 | ]); 9 | 10 | $benchmark->run('FastRoute*', function() use ($box, $generator, $ids, $routes) { 11 | 12 | $addRoutes = box('fast-route-classic')->get('add-routes'); 13 | $router = $addRoutes($routes); 14 | 15 | $strategy = box('benchmark')->get('strategy'); 16 | 17 | foreach ($generator->methods() as $method) { 18 | $id = $strategy($ids, $method); 19 | 20 | $result = $router->dispatch($method, "/controller{$id}/action{$id}/{$id}/arg1/arg2"); 21 | $params = $result[2]; 22 | 23 | if ($params['id'] !== (string) $id) { 24 | return false; 25 | } 26 | } 27 | }); -------------------------------------------------------------------------------- /benchmarks/laravel/path.php: -------------------------------------------------------------------------------- 1 | template('/controller{%id%}/action{%id%}/{id}/{arg1}/{arg2}', [ 5 | 'id' => '[^/]+', 6 | 'arg1' => '[^/]+', 7 | 'arg2' => '[^/]+' 8 | ]); 9 | 10 | $buildRoutes = box('laravel')->get('build-routes'); 11 | 12 | list($ids, $routes) = $buildRoutes($generator, [ 13 | 'isolated' => box('benchmark')->get('isolated') 14 | ]); 15 | 16 | $benchmark->run('Laravel', function() use ($box, $generator, $ids, $routes) { 17 | 18 | $addRoutes = box('laravel')->get('add-routes'); 19 | $router = $addRoutes($routes); 20 | 21 | $strategy = box('benchmark')->get('strategy'); 22 | 23 | foreach ($generator->methods() as $method) { 24 | $id = $strategy($ids, $method); 25 | 26 | $request = Request::create("/controller{$id}/action{$id}/{$id}/arg1/arg2", $method); 27 | $result = $router->dispatch($request)->getContent() ; 28 | 29 | if ($result !== (string) $id) { 30 | return false; 31 | } 32 | } 33 | }); 34 | -------------------------------------------------------------------------------- /benchmarks/laravel/subdomain.php: -------------------------------------------------------------------------------- 1 | host('subdomain{%hostId%}.domain.com', '*'); 5 | $generator->template('/controller{%id%}/action{%id%}/{id}/{arg1}/{arg2}', [ 6 | 'id' => '[^/]+', 7 | 'arg1' => '[^/]+', 8 | 'arg2' => '[^/]+' 9 | ]); 10 | 11 | $buildRoutes = box('laravel')->get('build-routes'); 12 | 13 | list($ids, $routes) = $buildRoutes($generator, [ 14 | 'nbHosts' => box('benchmark')->get('nbHosts'), 15 | 'isolated' => box('benchmark')->get('isolated') 16 | ]); 17 | 18 | $benchmark->run('Laravel', function() use ($box, $generator, $ids, $routes) { 19 | 20 | $addRoutes = box('laravel')->get('add-routes'); 21 | $router = $addRoutes($routes); 22 | 23 | $strategy = box('benchmark')->get('strategy'); 24 | 25 | foreach ($generator->hosts() as $host) { 26 | foreach ($generator->methods() as $method) { 27 | $id = $strategy($ids, $method, $host); 28 | 29 | $request = Request::create("/controller{$id}/action{$id}/{$id}/arg1/arg2", $method, [], [], [], [ 30 | 'HTTP_HOST' => $host 31 | ]); 32 | $result = $router->dispatch($request)->getContent() ; 33 | 34 | if ($result !== (string) $id) { 35 | return false; 36 | } 37 | } 38 | } 39 | }); 40 | -------------------------------------------------------------------------------- /benchmarks/li3/path.php: -------------------------------------------------------------------------------- 1 | template('/controller{%id%}/action{%id%}/{:id}/{:arg1}/{:arg2}'); 6 | 7 | $buildRoutes = box('li3')->get('build-routes'); 8 | 9 | list($ids, $routes) = $buildRoutes($generator, [ 10 | 'isolated' => box('benchmark')->get('isolated') 11 | ]); 12 | 13 | $benchmark->run('Li3', function() use ($box, $generator, $ids, $routes) { 14 | 15 | $addRoutes = box('li3')->get('add-routes'); 16 | $addRoutes($routes); 17 | 18 | $strategy = box('benchmark')->get('strategy'); 19 | 20 | foreach ($generator->methods() as $method) { 21 | $id = $strategy($ids, $method); 22 | 23 | $request = new Request([ 24 | 'url' => "/controller{$id}/action{$id}/{$id}/arg1/arg2", 25 | 'env' => [ 26 | 'REQUEST_METHOD' => $method 27 | ] 28 | ]); 29 | 30 | Router::parse($request); 31 | $params = $request->params; 32 | 33 | if ($params['id'] !== (string) $id) { 34 | return false; 35 | } 36 | } 37 | 38 | }); 39 | -------------------------------------------------------------------------------- /benchmarks/li3/subdomain.php: -------------------------------------------------------------------------------- 1 | host('subdomain{%hostId%}.domain.com', '*'); 6 | $generator->template('/controller{%id%}/action{%id%}/{:id}/{:arg1}/{:arg2}'); 7 | 8 | $buildRoutes = box('li3')->get('build-routes'); 9 | 10 | list($ids, $routes) = $buildRoutes($generator, [ 11 | 'nbHosts' => box('benchmark')->get('nbHosts'), 12 | 'isolated' => box('benchmark')->get('isolated') 13 | ]); 14 | 15 | $benchmark->run('Li3', function() use ($box, $generator, $ids, $routes) { 16 | 17 | $addRoutes = box('li3')->get('add-routes'); 18 | $addRoutes($routes); 19 | 20 | $strategy = box('benchmark')->get('strategy'); 21 | 22 | foreach ($generator->hosts() as $host) { 23 | foreach ($generator->methods() as $method) { 24 | $id = $strategy($ids, $method, $host); 25 | 26 | $request = new Request([ 27 | 'url' => "/controller{$id}/action{$id}/{$id}/arg1/arg2", 28 | 'env' => [ 29 | 'HTTP_HOST' => $host, 30 | 'REQUEST_METHOD' => $method 31 | ] 32 | ]); 33 | 34 | Router::parse($request); 35 | $params = $request->params; 36 | 37 | if ($params['id'] !== (string) $id) { 38 | return false; 39 | } 40 | } 41 | } 42 | 43 | }); 44 | -------------------------------------------------------------------------------- /benchmarks/phroute/path.php: -------------------------------------------------------------------------------- 1 | template('/controller{%id%}/action{%id%}/{id}/{arg1}/{arg2}'); 4 | 5 | $buildRoutes = box('phroute')->get('build-routes'); 6 | 7 | list($ids, $routes) = $buildRoutes($generator, [ 8 | 'isolated' => box('benchmark')->get('isolated') 9 | ]); 10 | 11 | $benchmark->run('PHRoute', function() use ($box, $generator, $ids, $routes) { 12 | 13 | $addRoutes = box('phroute')->get('add-routes'); 14 | $router = $addRoutes($routes); 15 | 16 | $strategy = box('benchmark')->get('strategy'); 17 | 18 | foreach ($generator->methods() as $method) { 19 | $id = $strategy($ids, $method); 20 | 21 | $result = $router->dispatch($method, "/controller{$id}/action{$id}/{$id}/arg1/arg2"); 22 | 23 | if ($result !== (string) $id) { 24 | return false; 25 | } 26 | } 27 | 28 | }); 29 | -------------------------------------------------------------------------------- /benchmarks/router/path.php: -------------------------------------------------------------------------------- 1 | template('/controller{%id%}/action{%id%}/{id}/{arg1}/{arg2}'); 4 | 5 | $buildRoutes = box('router')->get('build-routes'); 6 | 7 | list($ids, $routes) = $buildRoutes($generator, [ 8 | 'isolated' => box('benchmark')->get('isolated') 9 | ]); 10 | 11 | $benchmark->run('Router', function() use ($box, $generator, $ids, $routes) { 12 | 13 | $addRoutes = box('router')->get('add-routes'); 14 | $router = $addRoutes($routes); 15 | 16 | $strategy = box('benchmark')->get('strategy'); 17 | 18 | foreach ($generator->methods() as $method) { 19 | $id = $strategy($ids, $method); 20 | 21 | $route = $router->route("/controller{$id}/action{$id}/{$id}/arg1/arg2", $method); 22 | $params = $route->params; 23 | 24 | if ($params['id'] !== (string) $id) { 25 | return false; 26 | } 27 | } 28 | 29 | }); 30 | -------------------------------------------------------------------------------- /benchmarks/router/subdomain.php: -------------------------------------------------------------------------------- 1 | host('subdomain{%hostId%}.domain.com', '*'); 4 | $generator->template('/controller{%id%}/action{%id%}/{id}/{arg1}/{arg2}'); 5 | 6 | $buildRoutes = box('router')->get('build-routes'); 7 | 8 | list($ids, $routes) = $buildRoutes($generator, [ 9 | 'nbHosts' => box('benchmark')->get('nbHosts'), 10 | 'isolated' => box('benchmark')->get('isolated') 11 | ]); 12 | 13 | $benchmark->run('Router', function() use ($box, $generator, $ids, $routes) { 14 | 15 | $addRoutes = box('router')->get('add-routes'); 16 | $router = $addRoutes($routes); 17 | 18 | $strategy = box('benchmark')->get('strategy'); 19 | 20 | foreach ($generator->hosts() as $host) { 21 | foreach ($generator->methods() as $method) { 22 | $id = $strategy($ids, $method, $host); 23 | 24 | $route = $router->route("/controller{$id}/action{$id}/{$id}/arg1/arg2", $method, $host); 25 | $params = $route->params; 26 | 27 | if ($params['id'] !== (string) $id) { 28 | return false; 29 | } 30 | } 31 | } 32 | 33 | }); 34 | -------------------------------------------------------------------------------- /benchmarks/symfony/path.php: -------------------------------------------------------------------------------- 1 | template('/controller{%id%}/action{%id%}/{id}/{arg1}/{arg2}', [ 4 | 'id' => '[^/]+', 5 | 'arg1' => '[^/]+', 6 | 'arg2' => '[^/]+' 7 | ]); 8 | 9 | $buildRoutes = box('symfony')->get('build-routes'); 10 | 11 | list($ids, $routes) = $buildRoutes($generator, [ 12 | 'isolated' => box('benchmark')->get('isolated') 13 | ]); 14 | 15 | $benchmark->run('Symfony', function() use ($box, $generator, $ids, $routes) { 16 | 17 | $addRoutes = box('symfony')->get('add-routes'); 18 | $router = $addRoutes($routes); 19 | 20 | $strategy = box('benchmark')->get('strategy'); 21 | 22 | foreach ($generator->methods() as $method) { 23 | $id = $strategy($ids, $method); 24 | 25 | $router->getContext()->setMethod($method); 26 | $params = $router->match("/controller{$id}/action{$id}/{$id}/arg1/arg2"); 27 | 28 | if ($params['id'] !== (string) $id) { 29 | return false; 30 | } 31 | } 32 | }); 33 | -------------------------------------------------------------------------------- /benchmarks/symfony/subdomain.php: -------------------------------------------------------------------------------- 1 | host('subdomain{%hostId%}.domain.com', '*'); 4 | $generator->template('/controller{%id%}/action{%id%}/{id}/{arg1}/{arg2}', [ 5 | 'id' => '[^/]+', 6 | 'arg1' => '[^/]+', 7 | 'arg2' => '[^/]+' 8 | ]); 9 | 10 | $buildRoutes = box('symfony')->get('build-routes'); 11 | 12 | list($ids, $routes) = $buildRoutes($generator, [ 13 | 'nbHosts' => box('benchmark')->get('nbHosts'), 14 | 'isolated' => box('benchmark')->get('isolated') 15 | ]); 16 | 17 | $benchmark->run('Symfony', function() use ($box, $generator, $ids, $routes) { 18 | 19 | $addRoutes = box('symfony')->get('add-routes'); 20 | $router = $addRoutes($routes); 21 | 22 | $strategy = box('benchmark')->get('strategy'); 23 | 24 | foreach ($generator->hosts() as $host) { 25 | foreach ($generator->methods() as $method) { 26 | $id = $strategy($ids, $method, $host); 27 | 28 | $router->getContext()->setHost($host); 29 | $router->getContext()->setMethod($method); 30 | $params = $router->match("/controller{$id}/action{$id}/{$id}/arg1/arg2"); 31 | 32 | if ($params['id'] !== (string) $id) { 33 | return false; 34 | } 35 | } 36 | } 37 | }); 38 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "crysalead/php-router-benchmark", 3 | "description": "Benchmarking library", 4 | "keywords": ["Benchmark"], 5 | "license": "MIT", 6 | "repositories": [ 7 | { 8 | "type": "git", 9 | "url": "https://github.com/jails/FastRoute.git" 10 | } 11 | ], 12 | "require": { 13 | "php": ">=7.3 <7.4", 14 | "crysalead/router": "~3.0", 15 | "nikic/fast-route": "dev-master", 16 | "illuminate/routing": "~5.7", 17 | "crysalead/dir": "~2.0", 18 | "crysalead/benchmark": "dev-master", 19 | "crysalead/box": "~2.0", 20 | "symfony/routing": "~4.2", 21 | "crysalead/text": "~2.0", 22 | "phroute/phroute": "~2.1", 23 | "aura/router": "~3.1", 24 | "crysalead/net": "dev-master", 25 | "zendframework/zend-diactoros": "~1.3", 26 | "unionofrad/lithium": "^1.2", 27 | "illuminate/events": "~5.7", 28 | "cakephp/cakephp": "^3" 29 | }, 30 | "minimum-stability": "dev" 31 | } 32 | --------------------------------------------------------------------------------