1<?php 2 3/* 4 * This file is part of the Symfony package. 5 * 6 * (c) Fabien Potencier <fabien@symfony.com> 7 * 8 * For the full copyright and license information, please view the LICENSE 9 * file that was distributed with this source code. 10 */ 11 12namespace Symfony\Component\Routing\Tests\Matcher\Dumper; 13 14use PHPUnit\Framework\TestCase; 15use Symfony\Component\Routing\Matcher\Dumper\PhpMatcherDumper; 16use Symfony\Component\Routing\Matcher\RedirectableUrlMatcherInterface; 17use Symfony\Component\Routing\Matcher\UrlMatcher; 18use Symfony\Component\Routing\RequestContext; 19use Symfony\Component\Routing\Route; 20use Symfony\Component\Routing\RouteCollection; 21 22class PhpMatcherDumperTest extends TestCase 23{ 24 /** 25 * @var string 26 */ 27 private $matcherClass; 28 29 /** 30 * @var string 31 */ 32 private $dumpPath; 33 34 protected function setUp() 35 { 36 parent::setUp(); 37 38 $this->matcherClass = uniqid('ProjectUrlMatcher'); 39 $this->dumpPath = sys_get_temp_dir().\DIRECTORY_SEPARATOR.'php_matcher.'.$this->matcherClass.'.php'; 40 } 41 42 protected function tearDown() 43 { 44 parent::tearDown(); 45 46 @unlink($this->dumpPath); 47 } 48 49 public function testDumpWhenSchemeIsUsedWithoutAProperDumper() 50 { 51 $this->expectException('LogicException'); 52 $collection = new RouteCollection(); 53 $collection->add('secure', new Route( 54 '/secure', 55 [], 56 [], 57 [], 58 '', 59 ['https'] 60 )); 61 $dumper = new PhpMatcherDumper($collection); 62 $dumper->dump(); 63 } 64 65 public function testRedirectPreservesUrlEncoding() 66 { 67 $collection = new RouteCollection(); 68 $collection->add('foo', new Route('/foo:bar/')); 69 70 $class = $this->generateDumpedMatcher($collection, true); 71 72 $matcher = $this->getMockBuilder($class) 73 ->setMethods(['redirect']) 74 ->setConstructorArgs([new RequestContext()]) 75 ->getMock(); 76 77 $matcher->expects($this->once())->method('redirect')->with('/foo%3Abar/', 'foo')->willReturn([]); 78 79 $matcher->match('/foo%3Abar'); 80 } 81 82 /** 83 * @dataProvider getRouteCollections 84 */ 85 public function testDump(RouteCollection $collection, $fixture, $options = []) 86 { 87 $basePath = __DIR__.'/../../Fixtures/dumper/'; 88 89 $dumper = new PhpMatcherDumper($collection); 90 $this->assertStringEqualsFile($basePath.$fixture, $dumper->dump($options), '->dump() correctly dumps routes as optimized PHP code.'); 91 } 92 93 public function getRouteCollections() 94 { 95 /* test case 1 */ 96 97 $collection = new RouteCollection(); 98 99 $collection->add('overridden', new Route('/overridden')); 100 101 // defaults and requirements 102 $collection->add('foo', new Route( 103 '/foo/{bar}', 104 ['def' => 'test'], 105 ['bar' => 'baz|symfony'] 106 )); 107 // method requirement 108 $collection->add('bar', new Route( 109 '/bar/{foo}', 110 [], 111 [], 112 [], 113 '', 114 [], 115 ['GET', 'head'] 116 )); 117 // GET method requirement automatically adds HEAD as valid 118 $collection->add('barhead', new Route( 119 '/barhead/{foo}', 120 [], 121 [], 122 [], 123 '', 124 [], 125 ['GET'] 126 )); 127 // simple 128 $collection->add('baz', new Route( 129 '/test/baz' 130 )); 131 // simple with extension 132 $collection->add('baz2', new Route( 133 '/test/baz.html' 134 )); 135 // trailing slash 136 $collection->add('baz3', new Route( 137 '/test/baz3/' 138 )); 139 // trailing slash with variable 140 $collection->add('baz4', new Route( 141 '/test/{foo}/' 142 )); 143 // trailing slash and method 144 $collection->add('baz5', new Route( 145 '/test/{foo}/', 146 [], 147 [], 148 [], 149 '', 150 [], 151 ['post'] 152 )); 153 // complex name 154 $collection->add('baz.baz6', new Route( 155 '/test/{foo}/', 156 [], 157 [], 158 [], 159 '', 160 [], 161 ['put'] 162 )); 163 // defaults without variable 164 $collection->add('foofoo', new Route( 165 '/foofoo', 166 ['def' => 'test'] 167 )); 168 // pattern with quotes 169 $collection->add('quoter', new Route( 170 '/{quoter}', 171 [], 172 ['quoter' => '[\']+'] 173 )); 174 // space in pattern 175 $collection->add('space', new Route( 176 '/spa ce' 177 )); 178 179 // prefixes 180 $collection1 = new RouteCollection(); 181 $collection1->add('overridden', new Route('/overridden1')); 182 $collection1->add('foo1', new Route('/{foo}')); 183 $collection1->add('bar1', new Route('/{bar}')); 184 $collection1->addPrefix('/b\'b'); 185 $collection2 = new RouteCollection(); 186 $collection2->addCollection($collection1); 187 $collection2->add('overridden', new Route('/{var}', [], ['var' => '.*'])); 188 $collection1 = new RouteCollection(); 189 $collection1->add('foo2', new Route('/{foo1}')); 190 $collection1->add('bar2', new Route('/{bar1}')); 191 $collection1->addPrefix('/b\'b'); 192 $collection2->addCollection($collection1); 193 $collection2->addPrefix('/a'); 194 $collection->addCollection($collection2); 195 196 // overridden through addCollection() and multiple sub-collections with no own prefix 197 $collection1 = new RouteCollection(); 198 $collection1->add('overridden2', new Route('/old')); 199 $collection1->add('helloWorld', new Route('/hello/{who}', ['who' => 'World!'])); 200 $collection2 = new RouteCollection(); 201 $collection3 = new RouteCollection(); 202 $collection3->add('overridden2', new Route('/new')); 203 $collection3->add('hey', new Route('/hey/')); 204 $collection2->addCollection($collection3); 205 $collection1->addCollection($collection2); 206 $collection1->addPrefix('/multi'); 207 $collection->addCollection($collection1); 208 209 // "dynamic" prefix 210 $collection1 = new RouteCollection(); 211 $collection1->add('foo3', new Route('/{foo}')); 212 $collection1->add('bar3', new Route('/{bar}')); 213 $collection1->addPrefix('/b'); 214 $collection1->addPrefix('{_locale}'); 215 $collection->addCollection($collection1); 216 217 // route between collections 218 $collection->add('ababa', new Route('/ababa')); 219 220 // collection with static prefix but only one route 221 $collection1 = new RouteCollection(); 222 $collection1->add('foo4', new Route('/{foo}')); 223 $collection1->addPrefix('/aba'); 224 $collection->addCollection($collection1); 225 226 // prefix and host 227 228 $collection1 = new RouteCollection(); 229 230 $route1 = new Route('/route1', [], [], [], 'a.example.com'); 231 $collection1->add('route1', $route1); 232 233 $route2 = new Route('/c2/route2', [], [], [], 'a.example.com'); 234 $collection1->add('route2', $route2); 235 236 $route3 = new Route('/c2/route3', [], [], [], 'b.example.com'); 237 $collection1->add('route3', $route3); 238 239 $route4 = new Route('/route4', [], [], [], 'a.example.com'); 240 $collection1->add('route4', $route4); 241 242 $route5 = new Route('/route5', [], [], [], 'c.example.com'); 243 $collection1->add('route5', $route5); 244 245 $route6 = new Route('/route6', [], [], [], null); 246 $collection1->add('route6', $route6); 247 248 $collection->addCollection($collection1); 249 250 // host and variables 251 252 $collection1 = new RouteCollection(); 253 254 $route11 = new Route('/route11', [], [], [], '{var1}.example.com'); 255 $collection1->add('route11', $route11); 256 257 $route12 = new Route('/route12', ['var1' => 'val'], [], [], '{var1}.example.com'); 258 $collection1->add('route12', $route12); 259 260 $route13 = new Route('/route13/{name}', [], [], [], '{var1}.example.com'); 261 $collection1->add('route13', $route13); 262 263 $route14 = new Route('/route14/{name}', ['var1' => 'val'], [], [], '{var1}.example.com'); 264 $collection1->add('route14', $route14); 265 266 $route15 = new Route('/route15/{name}', [], [], [], 'c.example.com'); 267 $collection1->add('route15', $route15); 268 269 $route16 = new Route('/route16/{name}', ['var1' => 'val'], [], [], null); 270 $collection1->add('route16', $route16); 271 272 $route17 = new Route('/route17', [], [], [], null); 273 $collection1->add('route17', $route17); 274 275 $collection->addCollection($collection1); 276 277 // multiple sub-collections with a single route and a prefix each 278 $collection1 = new RouteCollection(); 279 $collection1->add('a', new Route('/a...')); 280 $collection2 = new RouteCollection(); 281 $collection2->add('b', new Route('/{var}')); 282 $collection3 = new RouteCollection(); 283 $collection3->add('c', new Route('/{var}')); 284 $collection3->addPrefix('/c'); 285 $collection2->addCollection($collection3); 286 $collection2->addPrefix('/b'); 287 $collection1->addCollection($collection2); 288 $collection1->addPrefix('/a'); 289 $collection->addCollection($collection1); 290 291 /* test case 2 */ 292 293 $redirectCollection = clone $collection; 294 295 // force HTTPS redirection 296 $redirectCollection->add('secure', new Route( 297 '/secure', 298 [], 299 [], 300 [], 301 '', 302 ['https'] 303 )); 304 305 // force HTTP redirection 306 $redirectCollection->add('nonsecure', new Route( 307 '/nonsecure', 308 [], 309 [], 310 [], 311 '', 312 ['http'] 313 )); 314 315 /* test case 3 */ 316 317 $rootprefixCollection = new RouteCollection(); 318 $rootprefixCollection->add('static', new Route('/test')); 319 $rootprefixCollection->add('dynamic', new Route('/{var}')); 320 $rootprefixCollection->addPrefix('rootprefix'); 321 $route = new Route('/with-condition'); 322 $route->setCondition('context.getMethod() == "GET"'); 323 $rootprefixCollection->add('with-condition', $route); 324 325 /* test case 4 */ 326 $headMatchCasesCollection = new RouteCollection(); 327 $headMatchCasesCollection->add('just_head', new Route( 328 '/just_head', 329 [], 330 [], 331 [], 332 '', 333 [], 334 ['HEAD'] 335 )); 336 $headMatchCasesCollection->add('head_and_get', new Route( 337 '/head_and_get', 338 [], 339 [], 340 [], 341 '', 342 [], 343 ['HEAD', 'GET'] 344 )); 345 $headMatchCasesCollection->add('get_and_head', new Route( 346 '/get_and_head', 347 [], 348 [], 349 [], 350 '', 351 [], 352 ['GET', 'HEAD'] 353 )); 354 $headMatchCasesCollection->add('post_and_head', new Route( 355 '/post_and_head', 356 [], 357 [], 358 [], 359 '', 360 [], 361 ['POST', 'HEAD'] 362 )); 363 $headMatchCasesCollection->add('put_and_post', new Route( 364 '/put_and_post', 365 [], 366 [], 367 [], 368 '', 369 [], 370 ['PUT', 'POST'] 371 )); 372 $headMatchCasesCollection->add('put_and_get_and_head', new Route( 373 '/put_and_post', 374 [], 375 [], 376 [], 377 '', 378 [], 379 ['PUT', 'GET', 'HEAD'] 380 )); 381 382 /* test case 5 */ 383 $groupOptimisedCollection = new RouteCollection(); 384 $groupOptimisedCollection->add('a_first', new Route('/a/11')); 385 $groupOptimisedCollection->add('a_second', new Route('/a/22')); 386 $groupOptimisedCollection->add('a_third', new Route('/a/333')); 387 $groupOptimisedCollection->add('a_wildcard', new Route('/{param}')); 388 $groupOptimisedCollection->add('a_fourth', new Route('/a/44/')); 389 $groupOptimisedCollection->add('a_fifth', new Route('/a/55/')); 390 $groupOptimisedCollection->add('a_sixth', new Route('/a/66/')); 391 $groupOptimisedCollection->add('nested_wildcard', new Route('/nested/{param}')); 392 $groupOptimisedCollection->add('nested_a', new Route('/nested/group/a/')); 393 $groupOptimisedCollection->add('nested_b', new Route('/nested/group/b/')); 394 $groupOptimisedCollection->add('nested_c', new Route('/nested/group/c/')); 395 396 $groupOptimisedCollection->add('slashed_a', new Route('/slashed/group/')); 397 $groupOptimisedCollection->add('slashed_b', new Route('/slashed/group/b/')); 398 $groupOptimisedCollection->add('slashed_c', new Route('/slashed/group/c/')); 399 400 $trailingSlashCollection = new RouteCollection(); 401 $trailingSlashCollection->add('simple_trailing_slash_no_methods', new Route('/trailing/simple/no-methods/', [], [], [], '', [], [])); 402 $trailingSlashCollection->add('simple_trailing_slash_GET_method', new Route('/trailing/simple/get-method/', [], [], [], '', [], ['GET'])); 403 $trailingSlashCollection->add('simple_trailing_slash_HEAD_method', new Route('/trailing/simple/head-method/', [], [], [], '', [], ['HEAD'])); 404 $trailingSlashCollection->add('simple_trailing_slash_POST_method', new Route('/trailing/simple/post-method/', [], [], [], '', [], ['POST'])); 405 $trailingSlashCollection->add('regex_trailing_slash_no_methods', new Route('/trailing/regex/no-methods/{param}/', [], [], [], '', [], [])); 406 $trailingSlashCollection->add('regex_trailing_slash_GET_method', new Route('/trailing/regex/get-method/{param}/', [], [], [], '', [], ['GET'])); 407 $trailingSlashCollection->add('regex_trailing_slash_HEAD_method', new Route('/trailing/regex/head-method/{param}/', [], [], [], '', [], ['HEAD'])); 408 $trailingSlashCollection->add('regex_trailing_slash_POST_method', new Route('/trailing/regex/post-method/{param}/', [], [], [], '', [], ['POST'])); 409 410 $trailingSlashCollection->add('simple_not_trailing_slash_no_methods', new Route('/not-trailing/simple/no-methods', [], [], [], '', [], [])); 411 $trailingSlashCollection->add('simple_not_trailing_slash_GET_method', new Route('/not-trailing/simple/get-method', [], [], [], '', [], ['GET'])); 412 $trailingSlashCollection->add('simple_not_trailing_slash_HEAD_method', new Route('/not-trailing/simple/head-method', [], [], [], '', [], ['HEAD'])); 413 $trailingSlashCollection->add('simple_not_trailing_slash_POST_method', new Route('/not-trailing/simple/post-method', [], [], [], '', [], ['POST'])); 414 $trailingSlashCollection->add('regex_not_trailing_slash_no_methods', new Route('/not-trailing/regex/no-methods/{param}', [], [], [], '', [], [])); 415 $trailingSlashCollection->add('regex_not_trailing_slash_GET_method', new Route('/not-trailing/regex/get-method/{param}', [], [], [], '', [], ['GET'])); 416 $trailingSlashCollection->add('regex_not_trailing_slash_HEAD_method', new Route('/not-trailing/regex/head-method/{param}', [], [], [], '', [], ['HEAD'])); 417 $trailingSlashCollection->add('regex_not_trailing_slash_POST_method', new Route('/not-trailing/regex/post-method/{param}', [], [], [], '', [], ['POST'])); 418 419 return [ 420 [new RouteCollection(), 'url_matcher0.php', []], 421 [$collection, 'url_matcher1.php', []], 422 [$redirectCollection, 'url_matcher2.php', ['base_class' => 'Symfony\Component\Routing\Tests\Fixtures\RedirectableUrlMatcher']], 423 [$rootprefixCollection, 'url_matcher3.php', []], 424 [$headMatchCasesCollection, 'url_matcher4.php', []], 425 [$groupOptimisedCollection, 'url_matcher5.php', ['base_class' => 'Symfony\Component\Routing\Tests\Fixtures\RedirectableUrlMatcher']], 426 [$trailingSlashCollection, 'url_matcher6.php', []], 427 [$trailingSlashCollection, 'url_matcher7.php', ['base_class' => 'Symfony\Component\Routing\Tests\Fixtures\RedirectableUrlMatcher']], 428 ]; 429 } 430 431 private function generateDumpedMatcher(RouteCollection $collection, $redirectableStub = false) 432 { 433 $options = ['class' => $this->matcherClass]; 434 435 if ($redirectableStub) { 436 $options['base_class'] = '\Symfony\Component\Routing\Tests\Matcher\Dumper\RedirectableUrlMatcherStub'; 437 } 438 439 $dumper = new PhpMatcherDumper($collection); 440 $code = $dumper->dump($options); 441 442 file_put_contents($this->dumpPath, $code); 443 include $this->dumpPath; 444 445 return $this->matcherClass; 446 } 447} 448 449abstract class RedirectableUrlMatcherStub extends UrlMatcher implements RedirectableUrlMatcherInterface 450{ 451 public function redirect($path, $route, $scheme = null) 452 { 453 } 454} 455