@@ -140,8 +140,9 @@ private function compileRoutes(RouteCollection $routes, $supportsRedirections)
140
140
$ code .= sprintf (" if (preg_match(%s, \$host, \$hostMatches)) { \n" , var_export ($ regex , true ));
141
141
}
142
142
143
+ $ groupCode = $ this ->compileStaticRoutes ($ collection , $ supportsRedirections );
143
144
$ tree = $ this ->buildStaticPrefixCollection ($ collection );
144
- $ groupCode = $ this ->compileStaticPrefixRoutes ($ tree , $ supportsRedirections );
145
+ $ groupCode . = $ this ->compileStaticPrefixRoutes ($ tree , $ supportsRedirections );
145
146
146
147
if (null !== $ regex ) {
147
148
// apply extra indention at each line (except empty ones)
@@ -176,6 +177,66 @@ private function buildStaticPrefixCollection(DumperCollection $collection)
176
177
return $ prefixCollection ;
177
178
}
178
179
180
+ /**
181
+ * Generates PHP code to match the static routes in a collection.
182
+ */
183
+ private function compileStaticRoutes (DumperCollection $ collection , bool $ supportsRedirections ): string
184
+ {
185
+ $ code = '' ;
186
+ $ dynamicRegex = array ();
187
+ $ dynamicRoutes = array ();
188
+ $ staticRoutes = array ();
189
+
190
+ foreach ($ collection ->all () as $ route ) {
191
+ $ compiledRoute = $ route ->getRoute ()->compile ();
192
+ $ regex = $ compiledRoute ->getRegex ();
193
+ $ methods = $ route ->getRoute ()->getMethods ();
194
+ if ($ hasTrailingSlash = $ supportsRedirections && $ pos = strpos ($ regex , '/$ ' )) {
195
+ $ regex = substr ($ regex , 0 , $ pos ).'/?$ ' .substr ($ regex , $ pos + 2 );
196
+ }
197
+ if (!$ compiledRoute ->getPathVariables ()) {
198
+ $ url = $ route ->getRoute ()->getPath ();
199
+ if ($ hasTrailingSlash ) {
200
+ $ url = rtrim ($ url , '/ ' );
201
+ }
202
+ foreach ($ dynamicRegex as $ rx ) {
203
+ if (preg_match ($ rx , $ url )) {
204
+ $ dynamicRegex [] = $ regex ;
205
+ $ dynamicRoutes [] = $ route ;
206
+ continue 2 ;
207
+ }
208
+ }
209
+
210
+ $ staticRoutes [$ url ][] = array ($ hasTrailingSlash , $ route );
211
+ } else {
212
+ $ dynamicRegex [] = $ regex ;
213
+ $ dynamicRoutes [] = $ route ;
214
+ }
215
+ }
216
+
217
+ $ collection ->setAll ($ dynamicRoutes );
218
+
219
+ if ($ staticRoutes ) {
220
+ foreach ($ staticRoutes as $ url => $ routes ) {
221
+ $ code .= sprintf (" case %s: \n" , var_export ($ url , true ));
222
+ foreach ($ routes as list ($ hasTrailingSlash , $ route )) {
223
+ $ methods = $ route ->getRoute ()->getMethods ();
224
+ $ supportsTrailingSlash = $ supportsRedirections && (!$ methods || in_array ('HEAD ' , $ methods ) || in_array ('GET ' , $ methods ));
225
+ $ routeCode = $ this ->compileRoute ($ route ->getRoute (), $ route ->getName (), $ supportsRedirections , null , $ hasTrailingSlash );
226
+ if ($ route ->getRoute ()->getCondition () || ($ hasTrailingSlash && !$ supportsTrailingSlash )) {
227
+ $ routeCode = preg_replace ('/^.{2,}$/m ' , ' $0 ' , $ routeCode );
228
+ }
229
+ $ code .= $ routeCode ;
230
+ }
231
+ $ code .= " break; \n" ;
232
+ }
233
+ $ code = preg_replace ('/^.{2,}$/m ' , ' $0 ' , $ code );
234
+ $ code = sprintf (" switch (%s) { \n{$ code } } \n\n" , $ supportsRedirections ? '$trimmedPathinfo ' : '$pathinfo ' );
235
+ }
236
+
237
+ return $ code ;
238
+ }
239
+
179
240
/**
180
241
* Generates PHP code to match a tree of routes.
181
242
*
@@ -222,28 +283,29 @@ private function compileStaticPrefixRoutes(StaticPrefixCollection $collection, $
222
283
* @param string $name The name of the Route
223
284
* @param bool $supportsRedirections Whether redirections are supported by the base class
224
285
* @param string|null $parentPrefix The prefix of the parent collection used to optimize the code
286
+ * @param bool|null $hasTrailingSlash Whether the path has a trailing slash when compiling a static route
225
287
*
226
288
* @return string PHP code
227
289
*
228
290
* @throws \LogicException
229
291
*/
230
- private function compileRoute (Route $ route , $ name , $ supportsRedirections , $ parentPrefix = null )
292
+ private function compileRoute (Route $ route , $ name , $ supportsRedirections , $ parentPrefix = null , bool $ hasTrailingSlash = null )
231
293
{
232
294
$ code = '' ;
233
295
$ compiledRoute = $ route ->compile ();
234
296
$ conditions = array ();
235
- $ hasTrailingSlash = false ;
236
297
$ matches = false ;
237
298
$ hostMatches = false ;
238
299
$ methods = $ route ->getMethods ();
239
300
240
301
$ supportsTrailingSlash = $ supportsRedirections && (!$ methods || in_array ('HEAD ' , $ methods ) || in_array ('GET ' , $ methods ));
241
302
$ regex = $ compiledRoute ->getRegex ();
242
303
243
- if (!count ($ compiledRoute ->getPathVariables ()) && false !== preg_match ('#^(.)\^(?P<url>.*?)\$\1# ' .('u ' === substr ($ regex , -1 ) ? 'u ' : '' ), $ regex , $ m )) {
244
- if ($ supportsTrailingSlash && '/ ' === substr ($ m ['url ' ], -1 )) {
304
+ if (null !== $ hasTrailingSlash && (!$ hasTrailingSlash || $ supportsTrailingSlash )) {
305
+ // no-op
306
+ } elseif (!$ compiledRoute ->getPathVariables () && preg_match ('#^(.)\^(?P<url>.*?)\$\1# ' .('u ' === $ regex [-1 ] ? 'u ' : '' ), $ regex , $ m )) {
307
+ if ($ hasTrailingSlash = $ supportsTrailingSlash && '/ ' === $ m ['url ' ][-1 ]) {
245
308
$ conditions [] = sprintf ('%s === $trimmedPathinfo ' , var_export (rtrim (str_replace ('\\' , '' , $ m ['url ' ]), '/ ' ), true ));
246
- $ hasTrailingSlash = true ;
247
309
} else {
248
310
$ conditions [] = sprintf ('%s === $pathinfo ' , var_export (str_replace ('\\' , '' , $ m ['url ' ]), true ));
249
311
}
@@ -252,9 +314,8 @@ private function compileRoute(Route $route, $name, $supportsRedirections, $paren
252
314
$ conditions [] = sprintf ('0 === strpos($pathinfo, %s) ' , var_export ($ compiledRoute ->getStaticPrefix (), true ));
253
315
}
254
316
255
- if ($ supportsTrailingSlash && $ pos = strpos ($ regex , '/$ ' )) {
317
+ if ($ hasTrailingSlash = $ supportsTrailingSlash && $ pos = strpos ($ regex , '/$ ' )) {
256
318
$ regex = substr ($ regex , 0 , $ pos ).'/?$ ' .substr ($ regex , $ pos + 2 );
257
- $ hasTrailingSlash = true ;
258
319
}
259
320
$ conditions [] = sprintf ('preg_match(%s, $pathinfo, $matches) ' , var_export ($ regex , true ));
260
321
@@ -271,11 +332,15 @@ private function compileRoute(Route $route, $name, $supportsRedirections, $paren
271
332
272
333
$ conditions = implode (' && ' , $ conditions );
273
334
274
- $ code .= <<<EOF
335
+ if ($ conditions ) {
336
+ $ code .= <<<EOF
275
337
// $ name
276
338
if ( $ conditions) {
277
339
278
340
EOF ;
341
+ } else {
342
+ $ code .= " // {$ name }\n" ;
343
+ }
279
344
280
345
$ gotoname = 'not_ ' .preg_replace ('/[^A-Za-z0-9_]/ ' , '' , $ name );
281
346
@@ -349,17 +414,17 @@ private function compileRoute(Route $route, $name, $supportsRedirections, $paren
349
414
$ code .= sprintf (
350
415
" \$ret = \$this->mergeDefaults(array_replace(%s), %s); \n" ,
351
416
implode (', ' , $ vars ),
352
- str_replace ( "\n" , '' , var_export ( $ route ->getDefaults (), true ))
417
+ self :: export ( $ route ->getDefaults ())
353
418
);
354
419
} elseif ($ route ->getDefaults ()) {
355
- $ code .= sprintf (" \$ret = %s; \n" , str_replace ( "\n" , '' , var_export ( array_replace ($ route ->getDefaults (), array ('_route ' => $ name )), true )));
420
+ $ code .= sprintf (" \$ret = %s; \n" , self :: export ( array_replace ($ route ->getDefaults (), array ('_route ' => $ name ))));
356
421
} else {
357
422
$ code .= sprintf (&q
106D2
uot; \$ret = array('_route' => '%s'); \n" , $ name );
358
423
}
359
424
360
425
if ($ hasTrailingSlash ) {
361
426
$ code .= <<<EOF
362
- if ('/' === substr( \$pathinfo, -1) ) {
427
+ if ('/' === \$pathinfo[-1] ) {
363
428
// no-op
364
429
} elseif ('GET' !== \$canonicalMethod) {
365
430
goto $ gotoname;
@@ -375,7 +440,7 @@ private function compileRoute(Route $route, $name, $supportsRedirections, $paren
375
440
if (!$ supportsRedirections ) {
376
441
throw new \LogicException ('The "schemes" requirement is only supported for URL matchers that implement RedirectableUrlMatcherInterface. ' );
377
442
}
378
- $ schemes = str_replace ( "\n" , '' , var_export ( array_flip ($ schemes), true ));
443
+ $ schemes = self :: export ( array_flip ($ schemes ));
379
444
$ code .= <<<EOF
380
445
\$requiredSchemes = $ schemes;
381
446
if (!isset( \$requiredSchemes[ \$context->getScheme()])) {
@@ -391,7 +456,11 @@ private function compileRoute(Route $route, $name, $supportsRedirections, $paren
391
456
} else {
392
457
$ code = substr_replace ($ code , 'return ' , $ retOffset , 6 );
393
458
}
394
- $ code .= " } \n" ;
459
+ if ($ conditions ) {
460
+ $ code .= " } \n" ;
461
+ } elseif ($ methods || $ hasTrailingSlash ) {
462
+ $ code .= ' ' ;
463
+ }
395
464
396
465
if ($ methods || $ hasTrailingSlash ) {
397
466
$ code .= " $ gotoname: \n" ;
@@ -440,4 +509,39 @@ private function getExpressionLanguage()
440
509
441
510
return $ this ->expressionLanguage ;
442
511
}
512
+
513
+ /**
514
+ * @internal
515
+ */
516
+ public static function export ($ value ): string
517
+ {
518
+ if (null === $ value ) {
519
+ return 'null ' ;
520
+ }
521
+ if (!\is_array ($ value )) {
522
+ return var_export ($ value , true );
523
+ }
524
+ if (!$ value ) {
525
+ return 'array() ' ;
526
+ }
527
+
528
+ $ i = 0 ;
529
+ $ export = 'array( ' ;
530
+
531
+ foreach ($ value as $ k => $ v ) {
532
+ if ($ i === $ k ) {
533
+ ++$ i ;
534
+ } else {
535
+ $ export .= var_export ($ k , true ).' => ' ;
536
+
537
+ if (\is_int ($ k ) && $ i < $ k ) {
538
+ $ i = 1 + $ k ;
539
+ }
540
+ }
541
+
542
+ $ export .= self ::export ($ v ).', ' ;
543
+ }
544
+
545
+ return substr_replace ($ export , ') ' , -2 );
546
+ }
443
547
}
0 commit comments