/src/Symfony/Bundle/FrameworkBundle/Routing/Router.php

https://github.com/gimler/symfony · PHP · 195 lines · 116 code · 27 blank · 52 comment · 9 complexity · a7829c5fe679bdb9c5482cb4d42b01ae MD5 · raw file

  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 Psr\Container\ContainerInterface;
  12. use Psr\Log\LoggerInterface;
  13. use Symfony\Component\Config\Loader\LoaderInterface;
  14. use Symfony\Component\DependencyInjection\Config\ContainerParametersResource;
  15. use Symfony\Component\DependencyInjection\ContainerInterface as SymfonyContainerInterface;
  16. use Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException;
  17. use Symfony\Component\DependencyInjection\Exception\RuntimeException;
  18. use Symfony\Component\DependencyInjection\ServiceSubscriberInterface;
  19. use Symfony\Component\HttpKernel\CacheWarmer\WarmableInterface;
  20. use Symfony\Component\Routing\RequestContext;
  21. use Symfony\Component\Routing\RouteCollection;
  22. use Symfony\Component\Routing\Router as BaseRouter;
  23. /**
  24. * This Router creates the Loader only when the cache is empty.
  25. *
  26. * @author Fabien Potencier <fabien@symfony.com>
  27. */
  28. class Router extends BaseRouter implements WarmableInterface, ServiceSubscriberInterface
  29. {
  30. private $container;
  31. private $collectedParameters = array();
  32. private $paramFetcher;
  33. /**
  34. * @param ContainerInterface $container A ContainerInterface instance
  35. * @param mixed $resource The main resource to load
  36. * @param array $options An array of options
  37. * @param RequestContext $context The context
  38. * @param ContainerInterface|null $parameters A ContainerInterface instance allowing to fetch parameters
  39. * @param LoggerInterface|null $logger
  40. */
  41. public function __construct(ContainerInterface $container, $resource, array $options = array(), RequestContext $context = null, ContainerInterface $parameters = null, LoggerInterface $logger = null)
  42. {
  43. $this->container = $container;
  44. $this->resource = $resource;
  45. $this->context = $context ?: new RequestContext();
  46. $this->logger = $logger;
  47. $this->setOptions($options);
  48. if ($parameters) {
  49. $this->paramFetcher = array($parameters, 'get');
  50. } elseif ($container instanceof SymfonyContainerInterface) {
  51. $this->paramFetcher = array($container, 'getParameter');
  52. } else {
  53. throw new \LogicException(sprintf('You should either pass a "%s" instance or provide the $parameters argument of the "%s" method.', SymfonyContainerInterface::class, __METHOD__));
  54. }
  55. }
  56. /**
  57. * {@inheritdoc}
  58. */
  59. public function getRouteCollection()
  60. {
  61. if (null === $this->collection) {
  62. $this->collection = $this->container->get('routing.loader')->load($this->resource, $this->options['resource_type']);
  63. $this->resolveParameters($this->collection);
  64. $this->collection->addResource(new ContainerParametersResource($this->collectedParameters));
  65. }
  66. return $this->collection;
  67. }
  68. /**
  69. * {@inheritdoc}
  70. */
  71. public function warmUp($cacheDir)
  72. {
  73. $currentDir = $this->getOption('cache_dir');
  74. // force cache generation
  75. $this->setOption('cache_dir', $cacheDir);
  76. $this->getMatcher();
  77. $this->getGenerator();
  78. $this->setOption('cache_dir', $currentDir);
  79. }
  80. /**
  81. * Replaces placeholders with service container parameter values in:
  82. * - the route defaults,
  83. * - the route requirements,
  84. * - the route path,
  85. * - the route host,
  86. * - the route schemes,
  87. * - the route methods.
  88. */
  89. private function resolveParameters(RouteCollection $collection)
  90. {
  91. foreach ($collection as $route) {
  92. foreach ($route->getDefaults() as $name => $value) {
  93. $route->setDefault($name, $this->resolve($value));
  94. }
  95. foreach ($route->getRequirements() as $name => $value) {
  96. $route->setRequirement($name, $this->resolve($value));
  97. }
  98. $route->setPath($this->resolve($route->getPath()));
  99. $route->setHost($this->resolve($route->getHost()));
  100. $schemes = array();
  101. foreach ($route->getSchemes() as $scheme) {
  102. $schemes = array_merge($schemes, explode('|', $this->resolve($scheme)));
  103. }
  104. $route->setSchemes($schemes);
  105. $methods = array();
  106. foreach ($route->getMethods() as $method) {
  107. $methods = array_merge($methods, explode('|', $this->resolve($method)));
  108. }
  109. $route->setMethods($methods);
  110. $route->setCondition($this->resolve($route->getCondition()));
  111. }
  112. }
  113. /**
  114. * Recursively replaces placeholders with the service container parameters.
  115. *
  116. * @param mixed $value The source which might contain "%placeholders%"
  117. *
  118. * @return mixed The source with the placeholders replaced by the container
  119. * parameters. Arrays are resolved recursively.
  120. *
  121. * @throws ParameterNotFoundException When a placeholder does not exist as a container parameter
  122. * @throws RuntimeException When a container value is not a string or a numeric value
  123. */
  124. private function resolve($value)
  125. {
  126. if (\is_array($value)) {
  127. foreach ($value as $key => $val) {
  128. $value[$key] = $this->resolve($val);
  129. }
  130. return $value;
  131. }
  132. if (!\is_string($value)) {
  133. return $value;
  134. }
  135. $escapedValue = preg_replace_callback('/%%|%([^%\s]++)%/', function ($match) use ($value) {
  136. // skip %%
  137. if (!isset($match[1])) {
  138. return '%%';
  139. }
  140. if (preg_match('/^env\(\w+\)$/', $match[1])) {
  141. throw new RuntimeException(sprintf('Using "%%%s%%" is not allowed in routing configuration.', $match[1]));
  142. }
  143. $resolved = ($this->paramFetcher)($match[1]);
  144. if (\is_string($resolved) || is_numeric($resolved)) {
  145. $this->collectedParameters[$match[1]] = $resolved;
  146. return (string) $resolved;
  147. }
  148. throw new RuntimeException(sprintf(
  149. 'The container parameter "%s", used in the route configuration value "%s", '.
  150. 'must be a string or numeric, but it is of type %s.',
  151. $match[1],
  152. $value,
  153. \gettype($resolved)
  154. )
  155. );
  156. }, $value);
  157. return str_replace('%%', '%', $escapedValue);
  158. }
  159. /**
  160. * {@inheritdoc}
  161. */
  162. public static function getSubscribedServices()
  163. {
  164. return array(
  165. 'routing.loader' => LoaderInterface::class,
  166. );
  167. }
  168. }