Subversion-Projekte lars-tiefland.php_share

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
1 lars 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: sfPatternRouting.class.php 32845 2011-07-28 11:50:48Z fabien $
20
 */
21
class sfPatternRouting extends sfRouting
22
{
23
  protected
24
    $currentRouteName   = null,
25
    $currentInternalUri = array(),
26
    $routes             = array(),
27
    $defaultParamsDirty = false,
28
    $cacheData          = array(),
29
    $cacheChanged       = false;
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
   *  * lookup_cache_dedicated_keys:      Whether to use dedicated keys for parse/generate cache (false by default)
43
   *                                      WARNING: When this option is activated, do not use sfFileCache; use a fast access
44
   *                                      cache backend (like sfAPCCache).
45
   *
46
   * @see sfRouting
47
   */
48
  public function initialize(sfEventDispatcher $dispatcher, sfCache $cache = null, $options = array())
49
  {
50
    $options = array_merge(array(
51
      'variable_prefixes'                => array(':'),
52
      'segment_separators'               => array('/', '.'),
53
      'variable_regex'                   => '[\w\d_]+',
54
      'load_configuration'               => false,
55
      'suffix'                           => '',
56
      'generate_shortest_url'            => true,
57
      'extra_parameters_as_query_string' => true,
58
      'lookup_cache_dedicated_keys'      => false,
59
    ), $options);
60
 
61
    // for BC
62
    if ('.' == $options['suffix'])
63
    {
64
      $options['suffix'] = '';
65
    }
66
 
67
    parent::initialize($dispatcher, $cache, $options);
68
 
69
    if (null !== $this->cache && !$options['lookup_cache_dedicated_keys'] && $cacheData = $this->cache->get('symfony.routing.data'))
70
    {
71
      $this->cacheData = unserialize($cacheData);
72
    }
73
  }
74
 
75
  /**
76
   * @see sfRouting
77
   */
78
  public function loadConfiguration()
79
  {
80
    if ($this->options['load_configuration'] && $config = $this->getConfigFilename())
81
    {
82
      include($config);
83
    }
84
 
85
    parent::loadConfiguration();
86
  }
87
 
88
  /**
89
   * Added for better performance. We need to ensure that changed default parameters
90
   * are set, but resetting them everytime wastes many cpu cycles
91
   */
92
  protected function ensureDefaultParametersAreSet()
93
  {
94
    if ($this->defaultParamsDirty)
95
    {
96
      foreach ($this->routes as $route)
97
      {
98
        $route->setDefaultParameters($this->defaultParameters);
99
      }
100
      $this->defaultParamsDirty = false;
101
    }
102
  }
103
 
104
  /**
105
   * @see sfRouting
106
   */
107
  public function setDefaultParameter($key, $value)
108
  {
109
    parent::setDefaultParameter($key, $value);
110
    $this->defaultParamsDirty = true;
111
  }
112
 
113
  /**
114
   * @see sfRouting
115
   */
116
  public function setDefaultParameters($parameters)
117
  {
118
    parent::setDefaultParameters($parameters);
119
    $this->defaultParamsDirty = true;
120
  }
121
 
122
  protected function getConfigFileName()
123
  {
124
    return sfContext::getInstance()->getConfigCache()->checkConfig('config/routing.yml', true);
125
  }
126
 
127
  /**
128
   * @see sfRouting
129
   */
130
  public function getCurrentInternalUri($withRouteName = false)
131
  {
132
    return null === $this->currentRouteName ? null : $this->currentInternalUri[$withRouteName ? 0 : 1];
133
  }
134
 
135
  /**
136
   * Gets the current route name.
137
   *
138
   * @return string The route name
139
   */
140
  public function getCurrentRouteName()
141
  {
142
    return $this->currentRouteName;
143
  }
144
 
145
  /**
146
   * @see sfRouting
147
   */
148
  public function getRoutes()
149
  {
150
    return $this->routes;
151
  }
152
 
153
  /**
154
   * @see sfRouting
155
   */
156
  public function setRoutes($routes)
157
  {
158
    foreach ($routes as $name => $route)
159
    {
160
      $this->connect($name, $route);
161
    }
162
  }
163
 
164
  /**
165
   * @see sfRouting
166
   */
167
  public function hasRoutes()
168
  {
169
    return count($this->routes) ? true : false;
170
  }
171
 
172
  /**
173
   * @see sfRouting
174
   */
175
  public function clearRoutes()
176
  {
177
    if ($this->options['logging'])
178
    {
179
      $this->dispatcher->notify(new sfEvent($this, 'application.log', array('Clear all current routes')));
180
    }
181
 
182
    $this->routes = array();
183
  }
184
 
185
  /**
186
   * Returns true if the route name given is defined.
187
   *
188
   * @param  string $name  The route name
189
   *
190
   * @return boolean
191
   */
192
  public function hasRouteName($name)
193
  {
194
    return isset($this->routes[$name]) ? true : false;
195
  }
196
 
197
  /**
198
   * Adds a new route at the beginning of the current list of routes.
199
   *
200
   * @see connect
201
   */
202
  public function prependRoute($name, $route)
203
  {
204
    $routes = $this->routes;
205
    $this->routes = array();
206
    $this->connect($name, $route);
207
    $this->routes = array_merge($this->routes, $routes);
208
  }
209
 
210
  /**
211
   * Adds a new route.
212
   *
213
   * Alias for the connect method.
214
   *
215
   * @see connect
216
   */
217
  public function appendRoute($name, $route)
218
  {
219
    return $this->connect($name, $route);
220
  }
221
 
222
  /**
223
   * Adds a new route before a given one in the current list of routes.
224
   *
225
   * @see connect
226
   */
227
  public function insertRouteBefore($pivot, $name, $route)
228
  {
229
    if (!isset($this->routes[$pivot]))
230
    {
231
      throw new sfConfigurationException(sprintf('Unable to insert route "%s" before inexistent route "%s".', $name, $pivot));
232
    }
233
 
234
    $routes = $this->routes;
235
    $this->routes = array();
236
    $newroutes = array();
237
    foreach ($routes as $key => $value)
238
    {
239
      if ($key == $pivot)
240
      {
241
        $this->connect($name, $route);
242
        $newroutes = array_merge($newroutes, $this->routes);
243
      }
244
      $newroutes[$key] = $value;
245
    }
246
 
247
    $this->routes = $newroutes;
248
  }
249
 
250
  /**
251
   * Adds a new route at the end of the current list of routes.
252
   *
253
   * A route string is a string with 2 special constructions:
254
   * - :string: :string denotes a named parameter (available later as $request->getParameter('string'))
255
   * - *: * match an indefinite number of parameters in a route
256
   *
257
   * Here is a very common rule in a symfony project:
258
   *
259
   * <code>
260
   * $r->connect('default', new sfRoute('/:module/:action/*'));
261
   * </code>
262
   *
263
   * @param  string  $name  The route name
264
   * @param  sfRoute $route A sfRoute instance
265
   *
266
   * @return array  current routes
267
   */
268
  public function connect($name, $route)
269
  {
270
    $routes = $route instanceof sfRouteCollection ? $route : array($name => $route);
271
    foreach (self::flattenRoutes($routes) as $name => $route)
272
    {
273
      $this->routes[$name] = $route;
274
      $this->configureRoute($route);
275
 
276
      if ($this->options['logging'])
277
      {
278
        $this->dispatcher->notify(new sfEvent($this, 'application.log', array(sprintf('Connect %s "%s" (%s)', get_class($route), $name, $route->getPattern()))));
279
      }
280
    }
281
  }
282
 
283
  public function configureRoute(sfRoute $route)
284
  {
285
    $route->setDefaultParameters($this->defaultParameters);
286
    $route->setDefaultOptions($this->options);
287
  }
288
 
289
  /**
290
   * @see sfRouting
291
   */
292
  public function generate($name, $params = array(), $absolute = false)
293
  {
294
    // fetch from cache
295
    if (null !== $this->cache)
296
    {
297
      $cacheKey = 'generate_'.$name.'_'.md5(serialize(array_merge($this->defaultParameters, $params))).'_'.md5(serialize($this->options['context']));
298
      if ($this->options['lookup_cache_dedicated_keys'] && $url = $this->cache->get('symfony.routing.data.'.$cacheKey))
299
      {
300
        return $this->fixGeneratedUrl($url, $absolute);
301
      }
302
      elseif (isset($this->cacheData[$cacheKey]))
303
      {
304
        return $this->fixGeneratedUrl($this->cacheData[$cacheKey], $absolute);
305
      }
306
    }
307
 
308
    if ($name)
309
    {
310
      // named route
311
      if (!isset($this->routes[$name]))
312
      {
313
        throw new sfConfigurationException(sprintf('The route "%s" does not exist.', $name));
314
      }
315
      $route = $this->routes[$name];
316
      $this->ensureDefaultParametersAreSet();
317
    }
318
    else
319
    {
320
      // find a matching route
321
      if (false === $route = $this->getRouteThatMatchesParameters($params))
322
      {
323
        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))));
324
      }
325
    }
326
 
327
    $url = $route->generate($params, $this->options['context'], $absolute);
328
 
329
    // store in cache
330
    if (null !== $this->cache)
331
    {
332
      if ($this->options['lookup_cache_dedicated_keys'])
333
      {
334
        $this->cache->set('symfony.routing.data.'.$cacheKey, $url);
335
      }
336
      else
337
      {
338
        $this->cacheChanged = true;
339
        $this->cacheData[$cacheKey] = $url;
340
      }
341
    }
342
 
343
    return $this->fixGeneratedUrl($url, $absolute);
344
  }
345
 
346
  /**
347
   * @see sfRouting
348
   */
349
  public function parse($url)
350
  {
351
    if (false === $info = $this->findRoute($url))
352
    {
353
      $this->currentRouteName = null;
354
      $this->currentInternalUri = array();
355
 
356
      return false;
357
    }
358
 
359
    if ($this->options['logging'])
360
    {
361
      $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))))));
362
    }
363
 
364
    // store the current internal URI
365
    $this->updateCurrentInternalUri($info['name'], $info['parameters']);
366
 
367
    $route = $this->routes[$info['name']];
368
 
369
    $this->ensureDefaultParametersAreSet();
370
 
371
    $route->bind($this->options['context'], $info['parameters']);
372
    $info['parameters']['_sf_route'] = $route;
373
 
374
    return $info['parameters'];
375
  }
376
 
377
  protected function updateCurrentInternalUri($name, array $parameters)
378
  {
379
    // store the route name
380
    $this->currentRouteName = $name;
381
 
382
    $internalUri = array('@'.$this->currentRouteName, $parameters['module'].'/'.$parameters['action']);
383
    unset($parameters['module'], $parameters['action']);
384
 
385
    $params = array();
386
    foreach ($parameters as $key => $value)
387
    {
388
      $params[] = $key.'='.$value;
389
    }
390
 
391
    // sort to guaranty unicity
392
    sort($params);
393
 
394
    $params = $params ? '?'.implode('&', $params) : '';
395
 
396
    $this->currentInternalUri = array($internalUri[0].$params, $internalUri[1].$params);
397
  }
398
 
399
  /**
400
   * Finds a matching route for given URL.
401
   *
402
   * Returns false if no route matches.
403
   *
404
   * Returned array contains:
405
   *
406
   *  - name:       name or alias of the route that matched
407
   *  - pattern:    the compiled pattern of the route that matched
408
   *  - parameters: array containing key value pairs of the request parameters including defaults
409
   *
410
   * @param  string $url     URL to be parsed
411
   *
412
   * @return array|false  An array with routing information or false if no route matched
413
   */
414
  public function findRoute($url)
415
  {
416
    $url = $this->normalizeUrl($url);
417
 
418
    // fetch from cache
419
    if (null !== $this->cache)
420
    {
421
      $cacheKey = 'parse_'.$url.'_'.md5(serialize($this->options['context']));
422
      if ($this->options['lookup_cache_dedicated_keys'] && $info = $this->cache->get('symfony.routing.data.'.$cacheKey))
423
      {
424
        return unserialize($info);
425
      }
426
      elseif (isset($this->cacheData[$cacheKey]))
427
      {
428
        return $this->cacheData[$cacheKey];
429
      }
430
    }
431
 
432
    $info = $this->getRouteThatMatchesUrl($url);
433
 
434
    // store in cache
435
    if (null !== $this->cache)
436
    {
437
      if ($this->options['lookup_cache_dedicated_keys'])
438
      {
439
        $this->cache->set('symfony.routing.data.'.$cacheKey, serialize($info));
440
      }
441
      else
442
      {
443
        $this->cacheChanged = true;
444
        $this->cacheData[$cacheKey] = $info;
445
      }
446
    }
447
 
448
    return $info;
449
  }
450
 
451
  static public function flattenRoutes($routes)
452
  {
453
    $flattenRoutes = array();
454
    foreach ($routes as $name => $route)
455
    {
456
      if ($route instanceof sfRouteCollection)
457
      {
458
        $flattenRoutes = array_merge($flattenRoutes, self::flattenRoutes($route));
459
      }
460
      else
461
      {
462
        $flattenRoutes[$name] = $route;
463
      }
464
    }
465
 
466
    return $flattenRoutes;
467
  }
468
 
469
  protected function getRouteThatMatchesUrl($url)
470
  {
471
    $this->ensureDefaultParametersAreSet();
472
    foreach ($this->routes as $name => $route)
473
    {
474
      if (false === $parameters = $route->matchesUrl($url, $this->options['context']))
475
      {
476
        continue;
477
      }
478
 
479
      return array('name' => $name, 'pattern' => $route->getPattern(), 'parameters' => $parameters);
480
    }
481
 
482
    return false;
483
  }
484
 
485
  protected function getRouteThatMatchesParameters($parameters)
486
  {
487
    $this->ensureDefaultParametersAreSet();
488
    foreach ($this->routes as $route)
489
    {
490
      if ($route->matchesParameters($parameters, $this->options['context']))
491
      {
492
        return $route;
493
      }
494
    }
495
 
496
    return false;
497
  }
498
 
499
  protected function normalizeUrl($url)
500
  {
501
    // an URL should start with a '/', mod_rewrite doesn't respect that, but no-mod_rewrite version does.
502
    if ('/' != substr($url, 0, 1))
503
    {
504
      $url = '/'.$url;
505
    }
506
 
507
    // we remove the query string
508
    if (false !== $pos = strpos($url, '?'))
509
    {
510
      $url = substr($url, 0, $pos);
511
    }
512
 
513
    // remove multiple /
514
    $url = preg_replace('#/+#', '/', $url);
515
 
516
    return $url;
517
  }
518
 
519
  /**
520
   * @see sfRouting
521
   */
522
  public function shutdown()
523
  {
524
    if (null !== $this->cache && $this->cacheChanged)
525
    {
526
      $this->cacheChanged = false;
527
      $this->cache->set('symfony.routing.data', serialize($this->cacheData));
528
    }
529
  }
530
}