Chris@0
|
1 <?php
|
Chris@0
|
2
|
Chris@0
|
3 /*
|
Chris@0
|
4 * This file is part of the Symfony package.
|
Chris@0
|
5 *
|
Chris@0
|
6 * (c) Fabien Potencier <fabien@symfony.com>
|
Chris@0
|
7 *
|
Chris@0
|
8 * For the full copyright and license information, please view the LICENSE
|
Chris@0
|
9 * file that was distributed with this source code.
|
Chris@0
|
10 */
|
Chris@0
|
11
|
Chris@0
|
12 namespace Symfony\Component\Routing;
|
Chris@0
|
13
|
Chris@17
|
14 use Psr\Log\LoggerInterface;
|
Chris@17
|
15 use Symfony\Component\Config\ConfigCacheFactory;
|
Chris@17
|
16 use Symfony\Component\Config\ConfigCacheFactoryInterface;
|
Chris@17
|
17 use Symfony\Component\Config\ConfigCacheInterface;
|
Chris@0
|
18 use Symfony\Component\Config\Loader\LoaderInterface;
|
Chris@17
|
19 use Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface;
|
Chris@17
|
20 use Symfony\Component\HttpFoundation\Request;
|
Chris@0
|
21 use Symfony\Component\Routing\Generator\ConfigurableRequirementsInterface;
|
Chris@17
|
22 use Symfony\Component\Routing\Generator\Dumper\GeneratorDumperInterface;
|
Chris@0
|
23 use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
Chris@17
|
24 use Symfony\Component\Routing\Matcher\Dumper\MatcherDumperInterface;
|
Chris@0
|
25 use Symfony\Component\Routing\Matcher\RequestMatcherInterface;
|
Chris@0
|
26 use Symfony\Component\Routing\Matcher\UrlMatcherInterface;
|
Chris@0
|
27
|
Chris@0
|
28 /**
|
Chris@0
|
29 * The Router class is an example of the integration of all pieces of the
|
Chris@0
|
30 * routing system for easier use.
|
Chris@0
|
31 *
|
Chris@0
|
32 * @author Fabien Potencier <fabien@symfony.com>
|
Chris@0
|
33 */
|
Chris@0
|
34 class Router implements RouterInterface, RequestMatcherInterface
|
Chris@0
|
35 {
|
Chris@0
|
36 /**
|
Chris@0
|
37 * @var UrlMatcherInterface|null
|
Chris@0
|
38 */
|
Chris@0
|
39 protected $matcher;
|
Chris@0
|
40
|
Chris@0
|
41 /**
|
Chris@0
|
42 * @var UrlGeneratorInterface|null
|
Chris@0
|
43 */
|
Chris@0
|
44 protected $generator;
|
Chris@0
|
45
|
Chris@0
|
46 /**
|
Chris@0
|
47 * @var RequestContext
|
Chris@0
|
48 */
|
Chris@0
|
49 protected $context;
|
Chris@0
|
50
|
Chris@0
|
51 /**
|
Chris@0
|
52 * @var LoaderInterface
|
Chris@0
|
53 */
|
Chris@0
|
54 protected $loader;
|
Chris@0
|
55
|
Chris@0
|
56 /**
|
Chris@0
|
57 * @var RouteCollection|null
|
Chris@0
|
58 */
|
Chris@0
|
59 protected $collection;
|
Chris@0
|
60
|
Chris@0
|
61 /**
|
Chris@0
|
62 * @var mixed
|
Chris@0
|
63 */
|
Chris@0
|
64 protected $resource;
|
Chris@0
|
65
|
Chris@0
|
66 /**
|
Chris@0
|
67 * @var array
|
Chris@0
|
68 */
|
Chris@17
|
69 protected $options = [];
|
Chris@0
|
70
|
Chris@0
|
71 /**
|
Chris@0
|
72 * @var LoggerInterface|null
|
Chris@0
|
73 */
|
Chris@0
|
74 protected $logger;
|
Chris@0
|
75
|
Chris@0
|
76 /**
|
Chris@0
|
77 * @var ConfigCacheFactoryInterface|null
|
Chris@0
|
78 */
|
Chris@0
|
79 private $configCacheFactory;
|
Chris@0
|
80
|
Chris@0
|
81 /**
|
Chris@0
|
82 * @var ExpressionFunctionProviderInterface[]
|
Chris@0
|
83 */
|
Chris@17
|
84 private $expressionLanguageProviders = [];
|
Chris@0
|
85
|
Chris@0
|
86 /**
|
Chris@0
|
87 * @param LoaderInterface $loader A LoaderInterface instance
|
Chris@0
|
88 * @param mixed $resource The main resource to load
|
Chris@0
|
89 * @param array $options An array of options
|
Chris@0
|
90 * @param RequestContext $context The context
|
Chris@0
|
91 * @param LoggerInterface $logger A logger instance
|
Chris@0
|
92 */
|
Chris@17
|
93 public function __construct(LoaderInterface $loader, $resource, array $options = [], RequestContext $context = null, LoggerInterface $logger = null)
|
Chris@0
|
94 {
|
Chris@0
|
95 $this->loader = $loader;
|
Chris@0
|
96 $this->resource = $resource;
|
Chris@0
|
97 $this->logger = $logger;
|
Chris@0
|
98 $this->context = $context ?: new RequestContext();
|
Chris@0
|
99 $this->setOptions($options);
|
Chris@0
|
100 }
|
Chris@0
|
101
|
Chris@0
|
102 /**
|
Chris@0
|
103 * Sets options.
|
Chris@0
|
104 *
|
Chris@0
|
105 * Available options:
|
Chris@0
|
106 *
|
Chris@0
|
107 * * cache_dir: The cache directory (or null to disable caching)
|
Chris@0
|
108 * * debug: Whether to enable debugging or not (false by default)
|
Chris@0
|
109 * * generator_class: The name of a UrlGeneratorInterface implementation
|
Chris@0
|
110 * * generator_base_class: The base class for the dumped generator class
|
Chris@0
|
111 * * generator_cache_class: The class name for the dumped generator class
|
Chris@0
|
112 * * generator_dumper_class: The name of a GeneratorDumperInterface implementation
|
Chris@0
|
113 * * matcher_class: The name of a UrlMatcherInterface implementation
|
Chris@0
|
114 * * matcher_base_class: The base class for the dumped matcher class
|
Chris@0
|
115 * * matcher_dumper_class: The class name for the dumped matcher class
|
Chris@0
|
116 * * matcher_cache_class: The name of a MatcherDumperInterface implementation
|
Chris@0
|
117 * * resource_type: Type hint for the main resource (optional)
|
Chris@0
|
118 * * strict_requirements: Configure strict requirement checking for generators
|
Chris@0
|
119 * implementing ConfigurableRequirementsInterface (default is true)
|
Chris@0
|
120 *
|
Chris@0
|
121 * @param array $options An array of options
|
Chris@0
|
122 *
|
Chris@0
|
123 * @throws \InvalidArgumentException When unsupported option is provided
|
Chris@0
|
124 */
|
Chris@0
|
125 public function setOptions(array $options)
|
Chris@0
|
126 {
|
Chris@17
|
127 $this->options = [
|
Chris@0
|
128 'cache_dir' => null,
|
Chris@0
|
129 'debug' => false,
|
Chris@0
|
130 'generator_class' => 'Symfony\\Component\\Routing\\Generator\\UrlGenerator',
|
Chris@0
|
131 'generator_base_class' => 'Symfony\\Component\\Routing\\Generator\\UrlGenerator',
|
Chris@0
|
132 'generator_dumper_class' => 'Symfony\\Component\\Routing\\Generator\\Dumper\\PhpGeneratorDumper',
|
Chris@0
|
133 'generator_cache_class' => 'ProjectUrlGenerator',
|
Chris@0
|
134 'matcher_class' => 'Symfony\\Component\\Routing\\Matcher\\UrlMatcher',
|
Chris@0
|
135 'matcher_base_class' => 'Symfony\\Component\\Routing\\Matcher\\UrlMatcher',
|
Chris@0
|
136 'matcher_dumper_class' => 'Symfony\\Component\\Routing\\Matcher\\Dumper\\PhpMatcherDumper',
|
Chris@0
|
137 'matcher_cache_class' => 'ProjectUrlMatcher',
|
Chris@0
|
138 'resource_type' => null,
|
Chris@0
|
139 'strict_requirements' => true,
|
Chris@17
|
140 ];
|
Chris@0
|
141
|
Chris@0
|
142 // check option names and live merge, if errors are encountered Exception will be thrown
|
Chris@17
|
143 $invalid = [];
|
Chris@0
|
144 foreach ($options as $key => $value) {
|
Chris@18
|
145 if (\array_key_exists($key, $this->options)) {
|
Chris@0
|
146 $this->options[$key] = $value;
|
Chris@0
|
147 } else {
|
Chris@0
|
148 $invalid[] = $key;
|
Chris@0
|
149 }
|
Chris@0
|
150 }
|
Chris@0
|
151
|
Chris@0
|
152 if ($invalid) {
|
Chris@0
|
153 throw new \InvalidArgumentException(sprintf('The Router does not support the following options: "%s".', implode('", "', $invalid)));
|
Chris@0
|
154 }
|
Chris@0
|
155 }
|
Chris@0
|
156
|
Chris@0
|
157 /**
|
Chris@0
|
158 * Sets an option.
|
Chris@0
|
159 *
|
Chris@0
|
160 * @param string $key The key
|
Chris@0
|
161 * @param mixed $value The value
|
Chris@0
|
162 *
|
Chris@0
|
163 * @throws \InvalidArgumentException
|
Chris@0
|
164 */
|
Chris@0
|
165 public function setOption($key, $value)
|
Chris@0
|
166 {
|
Chris@18
|
167 if (!\array_key_exists($key, $this->options)) {
|
Chris@0
|
168 throw new \InvalidArgumentException(sprintf('The Router does not support the "%s" option.', $key));
|
Chris@0
|
169 }
|
Chris@0
|
170
|
Chris@0
|
171 $this->options[$key] = $value;
|
Chris@0
|
172 }
|
Chris@0
|
173
|
Chris@0
|
174 /**
|
Chris@0
|
175 * Gets an option value.
|
Chris@0
|
176 *
|
Chris@0
|
177 * @param string $key The key
|
Chris@0
|
178 *
|
Chris@0
|
179 * @return mixed The value
|
Chris@0
|
180 *
|
Chris@0
|
181 * @throws \InvalidArgumentException
|
Chris@0
|
182 */
|
Chris@0
|
183 public function getOption($key)
|
Chris@0
|
184 {
|
Chris@18
|
185 if (!\array_key_exists($key, $this->options)) {
|
Chris@0
|
186 throw new \InvalidArgumentException(sprintf('The Router does not support the "%s" option.', $key));
|
Chris@0
|
187 }
|
Chris@0
|
188
|
Chris@0
|
189 return $this->options[$key];
|
Chris@0
|
190 }
|
Chris@0
|
191
|
Chris@0
|
192 /**
|
Chris@0
|
193 * {@inheritdoc}
|
Chris@0
|
194 */
|
Chris@0
|
195 public function getRouteCollection()
|
Chris@0
|
196 {
|
Chris@0
|
197 if (null === $this->collection) {
|
Chris@0
|
198 $this->collection = $this->loader->load($this->resource, $this->options['resource_type']);
|
Chris@0
|
199 }
|
Chris@0
|
200
|
Chris@0
|
201 return $this->collection;
|
Chris@0
|
202 }
|
Chris@0
|
203
|
Chris@0
|
204 /**
|
Chris@0
|
205 * {@inheritdoc}
|
Chris@0
|
206 */
|
Chris@0
|
207 public function setContext(RequestContext $context)
|
Chris@0
|
208 {
|
Chris@0
|
209 $this->context = $context;
|
Chris@0
|
210
|
Chris@0
|
211 if (null !== $this->matcher) {
|
Chris@0
|
212 $this->getMatcher()->setContext($context);
|
Chris@0
|
213 }
|
Chris@0
|
214 if (null !== $this->generator) {
|
Chris@0
|
215 $this->getGenerator()->setContext($context);
|
Chris@0
|
216 }
|
Chris@0
|
217 }
|
Chris@0
|
218
|
Chris@0
|
219 /**
|
Chris@0
|
220 * {@inheritdoc}
|
Chris@0
|
221 */
|
Chris@0
|
222 public function getContext()
|
Chris@0
|
223 {
|
Chris@0
|
224 return $this->context;
|
Chris@0
|
225 }
|
Chris@0
|
226
|
Chris@0
|
227 /**
|
Chris@0
|
228 * Sets the ConfigCache factory to use.
|
Chris@0
|
229 */
|
Chris@0
|
230 public function setConfigCacheFactory(ConfigCacheFactoryInterface $configCacheFactory)
|
Chris@0
|
231 {
|
Chris@0
|
232 $this->configCacheFactory = $configCacheFactory;
|
Chris@0
|
233 }
|
Chris@0
|
234
|
Chris@0
|
235 /**
|
Chris@0
|
236 * {@inheritdoc}
|
Chris@0
|
237 */
|
Chris@17
|
238 public function generate($name, $parameters = [], $referenceType = self::ABSOLUTE_PATH)
|
Chris@0
|
239 {
|
Chris@0
|
240 return $this->getGenerator()->generate($name, $parameters, $referenceType);
|
Chris@0
|
241 }
|
Chris@0
|
242
|
Chris@0
|
243 /**
|
Chris@0
|
244 * {@inheritdoc}
|
Chris@0
|
245 */
|
Chris@0
|
246 public function match($pathinfo)
|
Chris@0
|
247 {
|
Chris@0
|
248 return $this->getMatcher()->match($pathinfo);
|
Chris@0
|
249 }
|
Chris@0
|
250
|
Chris@0
|
251 /**
|
Chris@0
|
252 * {@inheritdoc}
|
Chris@0
|
253 */
|
Chris@0
|
254 public function matchRequest(Request $request)
|
Chris@0
|
255 {
|
Chris@0
|
256 $matcher = $this->getMatcher();
|
Chris@0
|
257 if (!$matcher instanceof RequestMatcherInterface) {
|
Chris@0
|
258 // fallback to the default UrlMatcherInterface
|
Chris@0
|
259 return $matcher->match($request->getPathInfo());
|
Chris@0
|
260 }
|
Chris@0
|
261
|
Chris@0
|
262 return $matcher->matchRequest($request);
|
Chris@0
|
263 }
|
Chris@0
|
264
|
Chris@0
|
265 /**
|
Chris@0
|
266 * Gets the UrlMatcher instance associated with this Router.
|
Chris@0
|
267 *
|
Chris@0
|
268 * @return UrlMatcherInterface A UrlMatcherInterface instance
|
Chris@0
|
269 */
|
Chris@0
|
270 public function getMatcher()
|
Chris@0
|
271 {
|
Chris@0
|
272 if (null !== $this->matcher) {
|
Chris@0
|
273 return $this->matcher;
|
Chris@0
|
274 }
|
Chris@0
|
275
|
Chris@0
|
276 if (null === $this->options['cache_dir'] || null === $this->options['matcher_cache_class']) {
|
Chris@0
|
277 $this->matcher = new $this->options['matcher_class']($this->getRouteCollection(), $this->context);
|
Chris@0
|
278 if (method_exists($this->matcher, 'addExpressionLanguageProvider')) {
|
Chris@0
|
279 foreach ($this->expressionLanguageProviders as $provider) {
|
Chris@0
|
280 $this->matcher->addExpressionLanguageProvider($provider);
|
Chris@0
|
281 }
|
Chris@0
|
282 }
|
Chris@0
|
283
|
Chris@0
|
284 return $this->matcher;
|
Chris@0
|
285 }
|
Chris@0
|
286
|
Chris@0
|
287 $cache = $this->getConfigCacheFactory()->cache($this->options['cache_dir'].'/'.$this->options['matcher_cache_class'].'.php',
|
Chris@0
|
288 function (ConfigCacheInterface $cache) {
|
Chris@0
|
289 $dumper = $this->getMatcherDumperInstance();
|
Chris@0
|
290 if (method_exists($dumper, 'addExpressionLanguageProvider')) {
|
Chris@0
|
291 foreach ($this->expressionLanguageProviders as $provider) {
|
Chris@0
|
292 $dumper->addExpressionLanguageProvider($provider);
|
Chris@0
|
293 }
|
Chris@0
|
294 }
|
Chris@0
|
295
|
Chris@17
|
296 $options = [
|
Chris@0
|
297 'class' => $this->options['matcher_cache_class'],
|
Chris@0
|
298 'base_class' => $this->options['matcher_base_class'],
|
Chris@17
|
299 ];
|
Chris@0
|
300
|
Chris@0
|
301 $cache->write($dumper->dump($options), $this->getRouteCollection()->getResources());
|
Chris@0
|
302 }
|
Chris@0
|
303 );
|
Chris@0
|
304
|
Chris@14
|
305 if (!class_exists($this->options['matcher_cache_class'], false)) {
|
Chris@14
|
306 require_once $cache->getPath();
|
Chris@14
|
307 }
|
Chris@0
|
308
|
Chris@0
|
309 return $this->matcher = new $this->options['matcher_cache_class']($this->context);
|
Chris@0
|
310 }
|
Chris@0
|
311
|
Chris@0
|
312 /**
|
Chris@0
|
313 * Gets the UrlGenerator instance associated with this Router.
|
Chris@0
|
314 *
|
Chris@0
|
315 * @return UrlGeneratorInterface A UrlGeneratorInterface instance
|
Chris@0
|
316 */
|
Chris@0
|
317 public function getGenerator()
|
Chris@0
|
318 {
|
Chris@0
|
319 if (null !== $this->generator) {
|
Chris@0
|
320 return $this->generator;
|
Chris@0
|
321 }
|
Chris@0
|
322
|
Chris@0
|
323 if (null === $this->options['cache_dir'] || null === $this->options['generator_cache_class']) {
|
Chris@0
|
324 $this->generator = new $this->options['generator_class']($this->getRouteCollection(), $this->context, $this->logger);
|
Chris@0
|
325 } else {
|
Chris@0
|
326 $cache = $this->getConfigCacheFactory()->cache($this->options['cache_dir'].'/'.$this->options['generator_cache_class'].'.php',
|
Chris@0
|
327 function (ConfigCacheInterface $cache) {
|
Chris@0
|
328 $dumper = $this->getGeneratorDumperInstance();
|
Chris@0
|
329
|
Chris@17
|
330 $options = [
|
Chris@0
|
331 'class' => $this->options['generator_cache_class'],
|
Chris@0
|
332 'base_class' => $this->options['generator_base_class'],
|
Chris@17
|
333 ];
|
Chris@0
|
334
|
Chris@0
|
335 $cache->write($dumper->dump($options), $this->getRouteCollection()->getResources());
|
Chris@0
|
336 }
|
Chris@0
|
337 );
|
Chris@0
|
338
|
Chris@14
|
339 if (!class_exists($this->options['generator_cache_class'], false)) {
|
Chris@14
|
340 require_once $cache->getPath();
|
Chris@14
|
341 }
|
Chris@0
|
342
|
Chris@0
|
343 $this->generator = new $this->options['generator_cache_class']($this->context, $this->logger);
|
Chris@0
|
344 }
|
Chris@0
|
345
|
Chris@0
|
346 if ($this->generator instanceof ConfigurableRequirementsInterface) {
|
Chris@0
|
347 $this->generator->setStrictRequirements($this->options['strict_requirements']);
|
Chris@0
|
348 }
|
Chris@0
|
349
|
Chris@0
|
350 return $this->generator;
|
Chris@0
|
351 }
|
Chris@0
|
352
|
Chris@0
|
353 public function addExpressionLanguageProvider(ExpressionFunctionProviderInterface $provider)
|
Chris@0
|
354 {
|
Chris@0
|
355 $this->expressionLanguageProviders[] = $provider;
|
Chris@0
|
356 }
|
Chris@0
|
357
|
Chris@0
|
358 /**
|
Chris@0
|
359 * @return GeneratorDumperInterface
|
Chris@0
|
360 */
|
Chris@0
|
361 protected function getGeneratorDumperInstance()
|
Chris@0
|
362 {
|
Chris@0
|
363 return new $this->options['generator_dumper_class']($this->getRouteCollection());
|
Chris@0
|
364 }
|
Chris@0
|
365
|
Chris@0
|
366 /**
|
Chris@0
|
367 * @return MatcherDumperInterface
|
Chris@0
|
368 */
|
Chris@0
|
369 protected function getMatcherDumperInstance()
|
Chris@0
|
370 {
|
Chris@0
|
371 return new $this->options['matcher_dumper_class']($this->getRouteCollection());
|
Chris@0
|
372 }
|
Chris@0
|
373
|
Chris@0
|
374 /**
|
Chris@0
|
375 * Provides the ConfigCache factory implementation, falling back to a
|
Chris@0
|
376 * default implementation if necessary.
|
Chris@0
|
377 *
|
Chris@17
|
378 * @return ConfigCacheFactoryInterface
|
Chris@0
|
379 */
|
Chris@0
|
380 private function getConfigCacheFactory()
|
Chris@0
|
381 {
|
Chris@0
|
382 if (null === $this->configCacheFactory) {
|
Chris@0
|
383 $this->configCacheFactory = new ConfigCacheFactory($this->options['debug']);
|
Chris@0
|
384 }
|
Chris@0
|
385
|
Chris@0
|
386 return $this->configCacheFactory;
|
Chris@0
|
387 }
|
Chris@0
|
388 }
|