@@ -112,7 +112,8 @@ private function generateMatchMethod(): string
112
112
$ code = <<<EOF
113
113
{
114
114
\$allow = \$allowSchemes = array();
115
- \$pathinfo = rawurldecode( \$rawPathinfo) ?: '/';
115
+ \$pathinfo = rawurldecode( \$pathinfo) ?: '/';
116
+ \$trimmedPathinfo = rtrim( \$pathinfo, '/') ?: '/';
116
117
\$context = \$this->context;
117
118
\$requestMethod = \$canonicalMethod = \$context->getMethod();
118
119
{$ fetchHost }
@@ -148,8 +149,8 @@ public function match($pathinfo)
148
149
} finally {
149
150
$this->context->setScheme($scheme);
150
151
}
151
- } elseif ('/' !== $pathinfo) {
152
- $pathinfo = '/' !== $pathinfo[-1] ? $pathinfo.'/' : substr($pathinfo, 0, -1) ;
152
+ } elseif ('/' !== $trimmedPathinfo = rtrim($ pathinfo, '/') ?: '/' ) {
153
+ $pathinfo = $trimmedPathinfo === $pathinfo ? $pathinfo.'/' : $trimmedPathinfo ;
153
154
if ($ret = $this->doMatch($pathinfo, $allow, $allowSchemes)) {
154
155
return $this->redirect($pathinfo, $ret['_route']) + $ret;
155
156
}
@@ -161,13 +162,13 @@ public function match($pathinfo)
161
162
throw new ResourceNotFoundException();
162
163
}
163
164
164
- private function doMatch(string $rawPathinfo , array &$allow = array(), array &$allowSchemes = array()): array
165
+ private function doMatch(string $pathinfo , array &$allow = array(), array &$allowSchemes = array()): array
165
166
166
167
EOF
167
168
.$ code ."\n return array(); \n } " ;
168
169
}
169
170
170
- return " public function match( \$rawPathinfo ) \n" .$ code ."\n throw \$allow ? new MethodNotAllowedException(array_keys( \$allow)) : new ResourceNotFoundException(); \n } " ;
171
+ return " public function match( \$pathinfo ) \n" .$ code ."\n throw \$allow ? new MethodNotAllowedException(array_keys( \$allow)) : new ResourceNotFoundException(); \n } " ;
171
172
}
172
173
173
174
/**
@@ -304,7 +305,7 @@ private function compileStaticRoutes(array $staticRoutes, bool $matchHost): stri
304
305
EOF ;
305
306
}
306
307
307
- return sprintf (" switch ( \$trimmedPathinfo = '/' !== \$ pathinfo && '/' === \$ pathinfo[-1] ? substr( \$ pathinfo, 0, -1) : \$ pathinfo ) { \n%s } \n\n" , $ this ->indent ($ code ));
308
+ return sprintf (" switch ( \$trimmedPathinfo) { \n%s } \n\n" , $ this ->indent ($ code ));
308
309
}
309
310
310
311
/**
@@ -408,8 +409,9 @@ private function compileDynamicRoutes(RouteCollection $collection, bool $matchHo
408
409
if ($ hasTrailingSlash = '/ ' !== $ regex && '/ ' === $ regex [-1 ]) {
409
410
$ regex = substr ($ regex , 0 , -1 );
410
411
}
412
+ $ hasTrailingVar = (bool ) preg_match ('#\{\w+\}/?$# ' , $ route ->getPath ());
411
413
412
- $ tree ->addRoute ($ regex , array ($ name , $ regex , $ state ->vars , $ route , $ hasTrailingSlash ));
414
+ $ tree ->addRoute ($ regex , array ($ name , $ regex , $ state ->vars , $ route , $ hasTrailingSlash, $ hasTrailingVar ));
413
415
}
414
416
415
417
$ code .= $ this ->compileStaticPrefixCollection ($ tree , $ state );
@@ -418,7 +420,7 @@ private function compileDynamicRoutes(RouteCollection $collection, bool $matchHo
418
420
$ code .= "\n .')' " ;
419
421
$ state ->regex .= ') ' ;
420
422
}
421
- $ rx = ")(?:/?) $} {$ modifiers }" ;
423
+ $ rx = ")/? $} {$ modifiers }" ;
422
424
$ code .= "\n .' {$ rx }', " ;
423
425
$ state ->regex .= $ rx ;
424
426
$ state ->markTail = 0 ;
@@ -438,7 +440,7 @@ private function compileDynamicRoutes(RouteCollection $collection, bool $matchHo
438
440
\$routes = array(
439
441
{$ this ->indent ($ state ->default , 4 )} );
440
442
441
- list( \$ret, \$vars, \$requiredMethods, \$requiredSchemes, \$hasTrailingSlash) = \$routes[ \$m];
443
+ list( \$ret, \$vars, \$requiredMethods, \$requiredSchemes, \$hasTrailingSlash, \$ hasTrailingVar ) = \$routes[ \$m];
442
444
{$ this ->compileSwitchDefault (true , $ matchHost )}
443
445
EOF ;
444
446
}
@@ -493,11 +495,11 @@ private function compileStaticPrefixCollection(StaticPrefixCollection $tree, \st
493
495
continue ;
494
496
}
495
497
496
- list ($ name , $ regex , $ vars , $ route , $ hasTrailingSlash ) = $ route ;
498
+ list ($ name , $ regex , $ vars , $ route , $ hasTrailingSlash, $ hasTrailingVar ) = $ route ;
497
499
$ compiledRoute = $ route ->compile ();
498
500
499
501
if ($ compiledRoute ->getRegex () === $ prevRegex ) {
500
- $ state ->switch = substr_replace ($ state ->switch , $ this ->compileRoute ($ route , $ name , false , $ hasTrailingSlash )."\n" , -19 , 0 );
502
+ $ state ->switch = substr_replace ($ state ->switch , $ this ->compileRoute ($ route , $ name , false , $ hasTrailingSlash, $ hasTrailingVar )."\n" , -19 , 0 );
501
503
continue ;
502
504
}
503
505
@@ -516,20 +518,21 @@ private function compileStaticPrefixCollection(StaticPrefixCollection $tree, \st
516
518
unset($ defaults ['_canonical_route ' ]);
517
519
}
518
520
$ state ->default .= sprintf (
519
- "%s => array(%s, %s, %s, %s, %s), \n" ,
521
+ "%s => array(%s, %s, %s, %s, %s, %s ), \n" ,
520
522
$ state ->mark ,
521
523
self ::export (array ('_route ' => $ name ) + $ defaults ),
522
524
self ::export ($ vars ),
523
525
self ::export (array_flip ($ route ->getMethods ()) ?: null ),
524
526
self ::export (array_flip ($ route ->getSchemes ()) ?: null ),
525
- self ::export ($ hasTrailingSlash )
527
+ self ::export ($ hasTrailingSlash ),
528
+ self ::export ($ hasTrailingVar )
526
529
);
527
530
} else {
528
531
$ prevRegex = $ compiledRoute ->getRegex ();
529
532
530
533
$ state ->switch .= <<<EOF
531
534
case {$ state ->mark }:
532
- {$ this ->compileRoute ($ route , $ name , false , $ hasTrailingSlash , $ vars )}
535
+ {$ this ->compileRoute ($ route , $ name , false , $ hasTrailingSlash , $ hasTrailingVar , $ vars )}
533
536
break;
534
537
535
538
EOF ;
@@ -544,67 +547,84 @@ private function compileStaticPrefixCollection(StaticPrefixCollection $tree, \st
544
547
*/
545
548
private function compileSwitchDefault (bool $ hasVars , bool $ matchHost ): string
546
549
{
547
- $ code = sprintf ("
548
- if ('/' !== \$pathinfo) {%s
549
- if ( \$hasTrailingSlash !== ('/' === \$pathinfo[-1])) {%s
550
- break;
550
+ if ($ this ->supportsRedirections ) {
551
+ $ code = <<<'EOF'
552
+
553
+ if ('GET' === $canonicalMethod && (!$requiredMethods || isset($requiredMethods['GET']))) {
554
+ return $allow = $allowSchemes = array();
551
555
}
552
- } \n" ,
553
- $ hasVars ? "
554
- if ('/' === \$pathinfo[-1]) {
555
- if (preg_match( \$regex, substr( \$pathinfo, 0, -1), \$n) && \$m === (int) \$n['MARK']) {
556
- \$matches = \$n;
557
- } else {
558
- \$hasTrailingSlash = true;
559
- }
560
- } \n" : '' ,
561
- $ this ->supportsRedirections ? "
562
- if ((! \$requiredMethods || isset( \$requiredMethods['GET'])) && 'GET' === \$canonicalMethod) {
563
- return \$allow = \$allowSchemes = array();
564
- } " : ''
556
+ EOF;
557
+ } else {
558
+ $ code = '' ;
559
+ }
560
+
561
+ $ code .= $ hasVars ? '
562
+ if ($trimmedPathinfo === $pathinfo || !$hasTrailingVar) {
563
+ break;
564
+ } ' : '
565
+ break; ' ;
566
+
567
+ $ code = sprintf (<<<'EOF'
568
+ if ('/' !== $pathinfo && $hasTrailingSlash === ($trimmedPathinfo === $pathinfo)) {%s
569
+ }
570
+
571
+ EOF
572
+ ,
573
+ $ code
565
574
);
566
575
567
576
if ($ hasVars ) {
568
- $ code .= <<<EOF
577
+ $ code = <<<'EOF'
578
+
579
+ if ($trimmedPathinfo === $pathinfo || !$hasTrailingVar) {
580
+ // no-op
581
+ } elseif (preg_match($regex, $trimmedPathinfo, $n) && $m === (int) $n['MARK']) {
582
+ $matches = $n;
583
+ } else {
584
+ $hasTrailingSlash = true;
585
+ }
569
586
570
- foreach ( \$vars as \$i => \$v) {
571
- if (isset( \$matches[1 + \$i])) {
572
- \$ret[ \$v] = \$matches[1 + \$i];
587
+ EOF
588
+ .$ code .<<<'EOF'
589
+
590
+ foreach ($vars as $i => $v) {
591
+ if (isset($matches[1 + $i])) {
592
+ $ret[$v] = $matches[1 + $i];
573
593
}
574
594
}
575
595
576
596
EOF;
577
597
} elseif ($ matchHost ) {
578
- $ code .= <<<EOF
598
+ $ code .= <<<' EOF'
579
599
580
- if ( \ $requiredHost) {
581
- if ('#' !== \ $requiredHost[0] ? \ $requiredHost !== \ $host : !preg_match( \ $requiredHost, \ $host, \ $hostMatches)) {
600
+ if ($requiredHost) {
601
+ if ('#' !== $requiredHost[0] ? $requiredHost !== $host : !preg_match($requiredHost, $host, $hostMatches)) {
582
602
break;
583
603
}
584
- if ('#' === \ $requiredHost[0] && \ $hostMatches) {
585
- \ $hostMatches['_route'] = \ $ret['_route'];
586
- \ $ret = \ $this->mergeDefaults( \ $hostMatches, \ $ret);
604
+ if ('#' === $requiredHost[0] && $hostMatches) {
605
+ $hostMatches['_route'] = $ret['_route'];
606
+ $ret = $this->mergeDefaults($hostMatches, $ret);
587
607
}
588
608
}
589
609
590
610
EOF;
591
611
}
592
612
593
- $ code .= <<<EOF
613
+ $ code .= <<<' EOF'
594
614
595
- \ $hasRequiredScheme = ! \ $requiredSchemes || isset( \ $requiredSchemes[ \ $context->getScheme()]);
596
- if ( \ $requiredMethods && !isset( \ $requiredMethods[ \ $canonicalMethod]) && !isset( \ $requiredMethods[ \ $requestMethod])) {
597
- if ( \ $hasRequiredScheme) {
598
- \ $allow += \ $requiredMethods;
615
+ $hasRequiredScheme = !$requiredSchemes || isset($requiredSchemes[$context->getScheme()]);
616
+ if ($requiredMethods && !isset($requiredMethods[$canonicalMethod]) && !isset($requiredMethods[$requestMethod])) {
617
+ if ($hasRequiredScheme) {
618
+ $allow += $requiredMethods;
599
619
}
600
620
break;
601
621
}
602
- if (! \ $hasRequiredScheme) {
603
- \ $allowSchemes += \ $requiredSchemes;
622
+ if (!$hasRequiredScheme) {
623
+ $allowSchemes += $requiredSchemes;
604
624
break;
605
625
}
606
626
607
- return \ $ret;
627
+ return $ret;
608
628
609
629
EOF;
610
630
@@ -616,52 +636,77 @@ private function compileSwitchDefault(bool $hasVars, bool $matchHost): string
616
636
*
617
637
* @throws \LogicException
618
638
*/
619
- private function compileRoute (Route $ route , string $ name , bool $ checkHost , bool $ hasTrailingSlash , array $ vars = null ): string
639
+ private function compileRoute (Route $ route , string $ name , bool $ checkHost , bool $ hasTrailingSlash , bool $ hasTrailingVar = false , array $ vars = null ): string
620
640
{
621
641
$ compiledRoute = $ route ->compile ();
622
642
$ conditions = array ();
623
643
$ matches = (bool ) $ compiledRoute ->getPathVariables ();
624
644
$ hostMatches = (bool ) $ compiledRoute ->getHostVariables ();
625
645
$ methods = array_flip ($ route ->getMethods ());
626
646
$ gotoname = 'not_ ' .preg_replace ('/[^A-Za-z0-9_]/ ' , '' , $ name );
627
- $ code = " // $ name " ;
647
+ $ code = " // $ name\n " ;
628
648
629
649
if ('/ ' === $ route ->getPath ()) {
630
- $ code .= "\n" ;
631
- } elseif (!$ matches ) {
632
- $ code .= sprintf ("
633
- if ('/' !== \$pathinfo && '/' %s \$pathinfo[-1]) {%s
634
- goto $ gotoname;
635
- } \n\n" ,
636
- $ hasTrailingSlash ? '!== ' : '=== ' ,
637
- $ this ->supportsRedirections && (!$ methods || isset ($ methods ['GET ' ])) ? "
638
- if ('GET' === \$canonicalMethod) {
639
- return \$allow = \$allowSchemes = array();
640
- } " : ''
650
+ // no-op
651
+ } elseif (!$ hasTrailingVar ) {
652
+ $ code .= sprintf (<<<'EOF'
653
+ if ('/' !== $pathinfo && $trimmedPathinfo %s $pathinfo) {%%s
654
+ goto %%s;
655
+ }
656
+ EOF
657
+ ,
658
+ $ hasTrailingSlash ? '=== ' : '!== '
641
659
);
642
660
} elseif ($ hasTrailingSlash ) {
643
- $ code .= sprintf ("
644
- if ('/' !== \$pathinfo[-1]) {%s
645
- goto $ gotoname;
646
- }
647
- if ('/' !== \$pathinfo && preg_match( \$regex, substr( \$pathinfo, 0, -1), \$n) && \$m === (int) \$n['MARK']) {
648
- \$matches = \$n;
649
- } \n\n" ,
650
- $ this ->supportsRedirections && (!$ methods || isset ($ methods ['GET ' ])) ? "
651
- if ('GET' === \$canonicalMethod) {
652
- return \$allow = \$allowSchemes = array();
653
- } " : ''
654
- );
661
+ $ code .= <<<'EOF'
662
+ if ('/' !== $pathinfo && $trimmedPathinfo === $pathinfo) {%s
663
+ goto %s;
664
+ }
665
+ if ('/' !== $pathinfo && preg_match($regex, $trimmedPathinfo, $n) && $m === (int) $n['MARK']) {
666
+ $matches = $n;
667
+ }
668
+ EOF;
669
+ } elseif ($ this ->supportsRedirections && (!$ methods || isset ($ methods ['GET ' ]))) {
670
+ $ code .= <<<'EOF'
671
+ $hasTrailingSlash = false;
672
+ if ($trimmedPathinfo === $pathinfo) {
673
+ // no-op
674
+ } elseif (preg_match($regex, $trimmedPathinfo, $n) && $m === (int) $n['MARK']) {
675
+ $matches = $n;
655
676
} else {
656
- $ code .= sprintf ("
657
- if ('/' !== \$pathinfo && '/' === \$pathinfo[-1] && preg_match( \$regex, substr( \$pathinfo, 0, -1), \$n) && \$m === (int) \$n['MARK']) {%s
658
- goto $ gotoname;
659
- } \n\n" ,
660
- $ this ->supportsRedirections && (!$ methods || isset ($ methods ['GET ' ])) ? "
661
- if ('GET' === \$canonicalMethod) {
662
- return \$allow = \$allowSchemes = array();
663
- } " : ''
664
- );
677
+ $hasTrailingSlash = true;
678
+ }
679
+
680
+ if ('/' !== $pathinfo && $hasTrailingSlash === ($trimmedPathinfo === $pathinfo)) {%s
681
+ if ($trimmedPathinfo === $pathinfo) {
682
+ goto %s;
683
+ }
684
+ }
685
+ EOF;
686
+ } else {
687
+ $ code .= <<<'EOF'
688
+ if ($trimmedPathinfo === $pathinfo) {
689
+ // no-op
690
+ } elseif (preg_match($regex, $trimmedPathinfo, $n) && $m === (int) $n['MARK']) {
691
+ $matches = $n;
692
+ } elseif ('/' !== $pathinfo) {
693
+ goto %2$s;
694
+ }
695
+ EOF;
696
+ }
697
+
698
+ if ($ this ->supportsRedirections && (!$ methods || isset ($ methods ['GET ' ]))) {
699
+ $ code = sprintf ($ code , <<<'EOF'
700
+
701
+ if ('GET' === $canonicalMethod) {
702
+ return $allow = $allowSchemes = array();
703
+ }
704
+ EOF
705
+ ,
706
+ $ gotoname
707
+ )."\n\n" ;
708
+ } elseif ('/ ' !== $ route ->getPath ()) {
709
+ $ code = sprintf ($ code , '' , $ gotoname )."\n\n" ;
665
710
}
666
711
667
712
if ($ vars ) {
0 commit comments