Router.php 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.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. namespace Symfony\Bundle\FrameworkBundle\Routing;
  11. use Symfony\Component\DependencyInjection\ContainerInterface;
  12. use Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException;
  13. use Symfony\Component\DependencyInjection\Exception\RuntimeException;
  14. use Symfony\Component\HttpKernel\CacheWarmer\WarmableInterface;
  15. use Symfony\Component\Routing\RequestContext;
  16. use Symfony\Component\Routing\RouteCollection;
  17. use Symfony\Component\Routing\Router as BaseRouter;
  18. /**
  19. * This Router creates the Loader only when the cache is empty.
  20. *
  21. * @author Fabien Potencier <fabien@symfony.com>
  22. */
  23. class Router extends BaseRouter implements WarmableInterface
  24. {
  25. private $container;
  26. /**
  27. * @param ContainerInterface $container A ContainerInterface instance
  28. * @param mixed $resource The main resource to load
  29. * @param array $options An array of options
  30. * @param RequestContext $context The context
  31. */
  32. public function __construct(ContainerInterface $container, $resource, array $options = array(), RequestContext $context = null)
  33. {
  34. $this->container = $container;
  35. $this->resource = $resource;
  36. $this->context = $context ?: new RequestContext();
  37. $this->setOptions($options);
  38. }
  39. /**
  40. * {@inheritdoc}
  41. */
  42. public function getRouteCollection()
  43. {
  44. if (null === $this->collection) {
  45. $this->collection = $this->container->get('routing.loader')->load($this->resource, $this->options['resource_type']);
  46. $this->resolveParameters($this->collection);
  47. }
  48. return $this->collection;
  49. }
  50. /**
  51. * {@inheritdoc}
  52. */
  53. public function warmUp($cacheDir)
  54. {
  55. $currentDir = $this->getOption('cache_dir');
  56. // force cache generation
  57. $this->setOption('cache_dir', $cacheDir);
  58. $this->getMatcher();
  59. $this->getGenerator();
  60. $this->setOption('cache_dir', $currentDir);
  61. }
  62. /**
  63. * Replaces placeholders with service container parameter values in:
  64. * - the route defaults,
  65. * - the route requirements,
  66. * - the route path,
  67. * - the route host,
  68. * - the route schemes,
  69. * - the route methods.
  70. */
  71. private function resolveParameters(RouteCollection $collection)
  72. {
  73. foreach ($collection as $route) {
  74. foreach ($route->getDefaults() as $name => $value) {
  75. $route->setDefault($name, $this->resolve($value));
  76. }
  77. foreach ($route->getRequirements() as $name => $value) {
  78. if ('_scheme' === $name || '_method' === $name) {
  79. continue; // ignore deprecated requirements to not trigger deprecation warnings
  80. }
  81. $route->setRequirement($name, $this->resolve($value));
  82. }
  83. $route->setPath($this->resolve($route->getPath()));
  84. $route->setHost($this->resolve($route->getHost()));
  85. $schemes = array();
  86. foreach ($route->getSchemes() as $scheme) {
  87. $schemes = array_merge($schemes, explode('|', $this->resolve($scheme)));
  88. }
  89. $route->setSchemes($schemes);
  90. $methods = array();
  91. foreach ($route->getMethods() as $method) {
  92. $methods = array_merge($methods, explode('|', $this->resolve($method)));
  93. }
  94. $route->setMethods($methods);
  95. $route->setCondition($this->resolve($route->getCondition()));
  96. }
  97. }
  98. /**
  99. * Recursively replaces placeholders with the service container parameters.
  100. *
  101. * @param mixed $value The source which might contain "%placeholders%"
  102. *
  103. * @return mixed The source with the placeholders replaced by the container
  104. * parameters. Arrays are resolved recursively.
  105. *
  106. * @throws ParameterNotFoundException When a placeholder does not exist as a container parameter
  107. * @throws RuntimeException When a container value is not a string or a numeric value
  108. */
  109. private function resolve($value)
  110. {
  111. if (\is_array($value)) {
  112. foreach ($value as $key => $val) {
  113. $value[$key] = $this->resolve($val);
  114. }
  115. return $value;
  116. }
  117. if (!\is_string($value)) {
  118. return $value;
  119. }
  120. $container = $this->container;
  121. $escapedValue = preg_replace_callback('/%%|%([^%\s]++)%/', function ($match) use ($container, $value) {
  122. // skip %%
  123. if (!isset($match[1])) {
  124. return '%%';
  125. }
  126. $resolved = $container->getParameter($match[1]);
  127. if (\is_string($resolved) || is_numeric($resolved)) {
  128. return (string) $resolved;
  129. }
  130. throw new RuntimeException(sprintf('The container parameter "%s", used in the route configuration value "%s", must be a string or numeric, but it is of type %s.', $match[1], $value, \gettype($resolved)));
  131. }, $value);
  132. return str_replace('%%', '%', $escapedValue);
  133. }
  134. }