Development

/branches/1.2/lib/routing/sfPatternRouting.class.php

You must first sign up to be able to contribute.

root/branches/1.2/lib/routing/sfPatternRouting.class.php

Revision 23180, 15.7 kB (checked in by FabianLange, 5 years ago)

[doc] [1.2, 1.3] fixed phpdoc return value for findRoute (closes #7380)

  • Property svn:mime-type set to text/x-php
  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
Line 
1 <?php
2
3 /*
4  * This file is part of the symfony package.
5  * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
6  *
7  * For the full copyright and license information, please view the LICENSE
8  * file that was distributed with this source code.
9  */
10
11 /**
12  * sfPatternRouting class controls the generation and parsing of URLs.
13  *
14  * It parses and generates URLs by delegating the work to an array of sfRoute objects.
15  *
16  * @package    symfony
17  * @subpackage routing
18  * @author     Fabien Potencier <fabien.potencier@symfony-project.com>
19  * @version    SVN: $Id$
20  */
21 class sfPatternRouting extends sfRouting
22 {
23   protected
24     $currentRouteName   = null,
25     $currentInternalUri = array(),
26     $routes             = array(),
27     $cacheData          = array(),
28     $cacheChanged       = false,
29     $routesFullyLoaded  = true;
30
31   /**
32    * Initializes this Routing.
33    *
34    * Available options:
35    *
36    *  * suffix:                           The default suffix
37    *  * variable_prefixes:                An array of characters that starts a variable name (: by default)
38    *  * segment_separators:               An array of allowed characters for segment separators (/ and . by default)
39    *  * variable_regex:                   A regex that match a valid variable name ([\w\d_]+ by default)
40    *  * generate_shortest_url:            Whether to generate the shortest URL possible (true by default)
41    *  * extra_parameters_as_query_string: Whether to generate extra parameters as a query string
42    *  * lazy_routes_deserialize:          Use lazy route deserialization optimization: not all routes are deserialized
43    *                                      upfront but on demand (false by default)
44    *  * lookup_cache_dedicated_keys:      Whether to use dedicated keys for parse/generate cache (false by default)
45    *                                      WARNING: When this option is activated, do not use sfFileCache; use a fast access
46    *                                      cache backend (like sfAPCCache).
47    *
48    * @see sfRouting
49    */
50   public function initialize(sfEventDispatcher $dispatcher, sfCache $cache = null, $options = array())
51   {
52     $options = array_merge(array(
53       'variable_prefixes'                => array(':'),
54       'segment_separators'               => array('/', '.'),
55       'variable_regex'                   => '[\w\d_]+',
56       'load_configuration'               => false,
57       'suffix'                           => '',
58       'generate_shortest_url'            => true,
59       'extra_parameters_as_query_string' => true,
60       'lazy_routes_deserialize'          => false,
61       'lookup_cache_dedicated_keys'      => false,
62     ), $options);
63
64     // for BC
65     if ('.' == $options['suffix'])
66     {
67       $options['suffix'] = '';
68     }
69
70     parent::initialize($dispatcher, $cache, $options);
71
72     if (!is_null($this->cache) && !$options['lookup_cache_dedicated_keys'] && $cacheData = $this->cache->get('symfony.routing.data'))
73     {
74       $this->cacheData = unserialize($cacheData);
75     }
76   }
77
78   /**
79    * @see sfRouting
80    */
81   public function loadConfiguration()
82   {
83     if (!is_null($this->cache) && $routes = $this->cache->get('symfony.routing.configuration'))
84     {
85       $this->routes = unserialize($routes);
86       $this->routesFullyLoaded = false;
87     }
88     else
89     {
90       if ($this->options['load_configuration'] && $config = sfContext::getInstance()->getConfigCache()->checkConfig('config/routing.yml', true))
91       {
92         $this->setRoutes(include($config));
93       }
94
95       parent::loadConfiguration();
96
97       if (!is_null($this->cache))
98       {
99         if (!$this->options['lazy_routes_deserialize'])
100         {
101           $this->cache->set('symfony.routing.configuration', serialize($this->routes));
102         }
103         else
104         {
105           $lazyMap = array();
106
107           foreach ($this->routes as $name => $route)
108           {
109             if (is_string($route))
110             {
111               $route = $this->loadRoute($name);
112             }
113
114             $lazyMap[$name] = serialize($route);
115           }
116
117           $this->cache->set('symfony.routing.configuration', serialize($lazyMap));
118         }
119       }
120     }
121   }
122
123   /**
124    * Load a lazy route from cache
125    *
126    * @param string $name The name of the route
127    *
128    * @return sfRoute The route instance unserialized from the cache
129    */
130   protected function loadRoute($name)
131   {
132     if (is_string($route = $this->routes[$name]))
133     {
134       $this->routes[$name] = unserialize($route);
135       $this->routes[$name]->setDefaultParameters($this->defaultParameters);
136
137       return $this->routes[$name];
138     }
139     else
140     {
141       return $route;
142     }
143   }
144
145   /**
146    * Load all lazy routes
147    *
148    * @return void
149    */
150   protected function loadRoutes()
151   {
152     if ($this->routesFullyLoaded)
153     {
154       return;
155     }
156
157     foreach ($this->routes as $name => $route)
158     {
159       if (is_string($route))
160       {
161         $this->loadRoute($name);
162       }
163     }
164
165     $this->routesFullyLoaded = true;
166   }
167
168   /**
169    * @see sfRouting
170    */
171   public function getCurrentInternalUri($withRouteName = false)
172   {
173     return is_null($this->currentRouteName) ? null : $this->currentInternalUri[$withRouteName ? 0 : 1];
174   }
175
176   /**
177    * Gets the current route name.
178    *
179    * @return string The route name
180    */
181   public function getCurrentRouteName()
182   {
183     return $this->currentRouteName;
184   }
185
186   /**
187    * @see sfRouting
188    */
189   public function getRoutes()
190   {
191     if (!$this->routesFullyLoaded)
192     {
193       $this->loadRoutes();
194     }
195     return $this->routes;
196   }
197
198   /**
199    * @see sfRouting
200    */
201   public function setRoutes($routes)
202   {
203     foreach ($routes as $name => $route)
204     {
205       $this->connect($name, $route);
206     }
207
208     if (!$this->routesFullyLoaded)
209     {
210       $this->loadRoutes();
211     }
212
213     return $this->routes;
214   }
215
216   /**
217    * @see sfRouting
218    */
219   public function hasRoutes()
220   {
221     return count($this->routes) ? true : false;
222   }
223
224   /**
225    * @see sfRouting
226    */
227   public function clearRoutes()
228   {
229     if ($this->options['logging'])
230     {
231       $this->dispatcher->notify(new sfEvent($this, 'application.log', array('Clear all current routes')));
232     }
233
234     $this->routes = array();
235   }
236
237   /**
238    * Returns true if the route name given is defined.
239    *
240    * @param  string $name  The route name
241    *
242    * @return boolean
243    */
244   public function hasRouteName($name)
245   {
246     return isset($this->routes[$name]) ? true : false;
247   }
248
249   /**
250    * Adds a new route at the beginning of the current list of routes.
251    *
252    * @see connect
253    */
254   public function prependRoute($name, $route)
255   {
256     $routes = $this->routes;
257     $this->routes = array();
258     $newroutes = $this->connect($name, $route);
259     $this->routes = array_merge($newroutes, $routes);
260
261     if (!$this->routesFullyLoaded)
262     {
263       $this->loadRoutes();
264     }
265
266     return $this->routes;
267   }
268
269   /**
270    * Adds a new route.
271    *
272    * Alias for the connect method.
273    *
274    * @see connect
275    */
276   public function appendRoute($name, $route)
277   {
278     return $this->connect($name, $route);
279   }
280
281   /**
282    * Adds a new route before a given one in the current list of routes.
283    *
284    * @see connect
285    */
286   public function insertRouteBefore($pivot, $name, $route)
287   {
288     if (!isset($this->routes[$pivot]))
289     {
290       throw new sfConfigurationException(sprintf('Unable to insert route "%s" before inexistent route "%s".', $name, $pivot));
291     }
292
293     $routes = $this->routes;
294     $this->routes = array();
295     $newroutes = array();
296     foreach ($routes as $key => $value)
297     {
298       if ($key == $pivot)
299       {
300         $newroutes = array_merge($newroutes, $this->connect($name, $route));
301       }
302       $newroutes[$key] = $value;
303     }
304
305     $this->routes = $newroutes;
306
307     if (!$this->routesFullyLoaded)
308     {
309       $this->loadRoutes();
310     }
311
312     return $this->routes;
313   }
314
315   /**
316    * Adds a new route at the end of the current list of routes.
317    *
318    * A route string is a string with 2 special constructions:
319    * - :string: :string denotes a named paramater (available later as $request->getParameter('string'))
320    * - *: * match an indefinite number of parameters in a route
321    *
322    * Here is a very common rule in a symfony project:
323    *
324    * <code>
325    * $r->connect('default', new sfRoute('/:module/:action/*'));
326    * </code>
327    *
328    * @param  string  $name  The route name
329    * @param  sfRoute $route A sfRoute instance
330    *
331    * @return array  current routes
332    */
333   public function connect($name, $route)
334   {
335     $routes = $route instanceof sfRouteCollection ? $route : array($name => $route);
336     foreach (self::flattenRoutes($routes) as $name => $route)
337     {
338       $this->routes[$name] = $route;
339       $this->configureRoute($route);
340
341       if ($this->options['logging'])
342       {
343         $this->dispatcher->notify(new sfEvent($this, 'application.log', array(sprintf('Connect %s "%s" (%s)', get_class($route), $name, $route->getPattern()))));
344       }
345     }
346
347     if (!$this->routesFullyLoaded)
348     {
349       $this->loadRoutes();
350     }
351
352     return $this->routes;
353   }
354
355   public function configureRoute(sfRoute $route)
356   {
357     $route->setDefaultParameters($this->defaultParameters);
358     $route->setDefaultOptions($this->options);
359   }
360
361   /**
362    * @see sfRouting
363    */
364   public function generate($name, $params = array(), $absolute = false)
365   {
366     // fetch from cache
367     if (!is_null($this->cache))
368     {
369       $cacheKey = 'generate_'.$name.'_'.md5(serialize(array_merge($this->defaultParameters, $params))).'_'.md5(serialize($this->options['context']));
370       if ($this->options['lookup_cache_dedicated_keys'] && $url = $this->cache->get('symfony.routing.data.'.$cacheKey))
371       {
372         return $this->fixGeneratedUrl($url, $absolute);
373       }
374       elseif (isset($this->cacheData[$cacheKey]))
375       {
376         return $this->fixGeneratedUrl($this->cacheData[$cacheKey], $absolute);
377       }
378     }
379
380     if ($name)
381     {
382       // named route
383       if (!isset($this->routes[$name]))
384       {
385         throw new sfConfigurationException(sprintf('The route "%s" does not exist.', $name));
386       }
387
388       $route = $this->routes[$name];
389
390       if (is_string($route))
391       {
392         $route = $this->loadRoute($name);
393       }
394       $route->setDefaultParameters($this->defaultParameters);
395     }
396     else
397     {
398       // find a matching route
399       if (false === $route = $this->getRouteThatMatchesParameters($params, $this->options['context']))
400       {
401         throw new sfConfigurationException(sprintf('Unable to find a matching route to generate url for params "%s".', is_object($params) ? 'Object('.get_class($params).')' : str_replace("\n", '', var_export($params, true))));
402       }
403     }
404
405     $url = $route->generate($params, $this->options['context'], $absolute);
406
407     // store in cache
408     if (!is_null($this->cache))
409     {
410       if ($this->options['lookup_cache_dedicated_keys'])
411       {
412         $this->cache->set('symfony.routing.data.'.$cacheKey, $url);
413       }
414       else
415       {
416         $this->cacheChanged = true;
417         $this->cacheData[$cacheKey] = $url;
418       }
419     }
420
421     return $this->fixGeneratedUrl($url, $absolute);
422   }
423
424   /**
425    * @see sfRouting
426    */
427   public function parse($url)
428   {
429     if (false === $info = $this->findRoute($url))
430     {
431       $this->currentRouteName = null;
432       $this->currentInternalUri = array();
433
434       return false;
435     }
436
437     if ($this->options['logging'])
438     {
439       $this->dispatcher->notify(new sfEvent($this, 'application.log', array(sprintf('Match route "%s" (%s) for %s with parameters %s', $info['name'], $info['pattern'], $url, str_replace("\n", '', var_export($info['parameters'], true))))));
440     }
441
442     // store the current internal URI
443     $this->updateCurrentInternalUri($info['name'], $info['parameters']);
444
445     $route = $this->routes[$info['name']];
446
447     if (is_string($route))
448     {
449       $route = $this->loadRoute($info['name']);
450     }
451     $route->setDefaultParameters($this->defaultParameters);
452
453     $route->bind($this->options['context'], $info['parameters']);
454     $info['parameters']['_sf_route'] = $route;
455
456     return $info['parameters'];
457   }
458
459   protected function updateCurrentInternalUri($name, array $parameters)
460   {
461     // store the route name
462     $this->currentRouteName = $name;
463
464     $internalUri = array('@'.$this->currentRouteName, $parameters['module'].'/'.$parameters['action']);
465     unset($parameters['module'], $parameters['action']);
466
467     $params = array();
468     foreach ($parameters as $key => $value)
469     {
470       $params[] = $key.'='.$value;
471     }
472
473     // sort to guaranty unicity
474     sort($params);
475
476     $params = $params ? '?'.implode('&', $params) : '';
477
478     $this->currentInternalUri = array($internalUri[0].$params, $internalUri[1].$params);
479   }
480
481   /**
482    * Finds a matching route for given URL.
483    *
484    * Returns false if no route matches.
485    *
486    * Returned array contains:
487    *
488    *  - name:       name or alias of the route that matched
489    *  - pattern:    the compiled pattern of the route that matched
490    *  - parameters: array containing key value pairs of the request parameters including defaults
491    *
492    * @param  string $url     URL to be parsed
493    *
494    * @return array|false  An array with routing information or false if no route matched
495    */
496   public function findRoute($url)
497   {
498     $url = $this->normalizeUrl($url);
499
500     // fetch from cache
501     if (!is_null($this->cache))
502     {
503       $cacheKey = 'parse_'.$url.'_'.md5(serialize($this->options['context']));
504       if ($this->options['lookup_cache_dedicated_keys'] && $info = $this->cache->get('symfony.routing.data.'.$cacheKey))
505       {
506         return unserialize($info);
507       }
508       elseif (isset($this->cacheData[$cacheKey]))
509       {
510         return $this->cacheData[$cacheKey];
511       }
512     }
513
514     $info = $this->getRouteThatMatchesUrl($url);
515
516     // store in cache
517     if (!is_null($this->cache))
518     {
519       if ($this->options['lookup_cache_dedicated_keys'])
520       {
521         $this->cache->set('symfony.routing.data.'.$cacheKey, serialize($info));
522       }
523       else
524       {
525         $this->cacheChanged = true;
526         $this->cacheData[$cacheKey] = $info;
527       }
528     }
529
530     return $info;
531   }
532
533   static public function flattenRoutes($routes)
534   {
535     $flattenRoutes = array();
536     foreach ($routes as $name => $route)
537     {
538       if ($route instanceof sfRouteCollection)
539       {
540         $flattenRoutes = array_merge($flattenRoutes, self::flattenRoutes($route));
541       }
542       else
543       {
544         $flattenRoutes[$name] = $route;
545       }
546     }
547
548     return $flattenRoutes;
549   }
550
551   protected function getRouteThatMatchesUrl($url)
552   {
553     foreach ($this->routes as $name => $route)
554     {
555       if (is_string($route))
556       {
557         $route = $this->loadRoute($name);
558       }
559       $route->setDefaultParameters($this->defaultParameters);
560
561       if (false === $parameters = $route->matchesUrl($url, $this->options['context']))
562       {
563         continue;
564       }
565
566       return array('name' => $name, 'pattern' => $route->getPattern(), 'parameters' => $parameters);
567     }
568
569     return false;
570   }
571
572   protected function getRouteThatMatchesParameters($parameters)
573   {
574     foreach ($this->routes as $name => $route)
575     {
576       if (is_string($route))
577       {
578         $route = $this->loadRoute($name);
579       }
580       $route->setDefaultParameters($this->defaultParameters);
581
582       if ($route->matchesParameters($parameters, $this->options['context']))
583       {
584         return $route;
585       }
586     }
587
588     return false;
589   }
590
591   protected function normalizeUrl($url)
592   {
593     // an URL should start with a '/', mod_rewrite doesn't respect that, but no-mod_rewrite version does.
594     if ('/' != substr($url, 0, 1))
595     {
596       $url = '/'.$url;
597     }
598
599     // we remove the query string
600     if (false !== $pos = strpos($url, '?'))
601     {
602       $url = substr($url, 0, $pos);
603     }
604
605     // remove multiple /
606     $url = preg_replace('#/+#', '/', $url);
607
608     return $url;
609   }
610
611   /**
612    * @see sfRouting
613    */
614   public function shutdown()
615   {
616     if (!is_null($this->cache) && $this->cacheChanged)
617     {
618       $this->cacheChanged = false;
619       $this->cache->set('symfony.routing.data', serialize($this->cacheData));
620     }
621   }
622 }
623
Note: See TracBrowser for help on using the browser.