Subversion Repositories php-qbpwcf

Rev

Details | Last modification | View Log | RSS feed

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