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\Loader;
13
 
14
use Doctrine\Common\Annotations\Reader;
15
use Symfony\Component\Config\Loader\LoaderInterface;
16
use Symfony\Component\Config\Loader\LoaderResolverInterface;
17
use Symfony\Component\Config\Resource\FileResource;
18
use Symfony\Component\Routing\Annotation\Route as RouteAnnotation;
19
use Symfony\Component\Routing\Route;
20
use Symfony\Component\Routing\RouteCollection;
21
 
22
/**
23
 * AnnotationClassLoader loads routing information from a PHP class and its methods.
24
 *
25
 * You need to define an implementation for the configureRoute() method. Most of the
26
 * time, this method should define some PHP callable to be called for the route
27
 * (a controller in MVC speak).
28
 *
29
 * The @Route annotation can be set on the class (for global parameters),
30
 * and on each method.
31
 *
32
 * The @Route annotation main value is the route path. The annotation also
33
 * recognizes several parameters: requirements, options, defaults, schemes,
34
 * methods, host, and name. The name parameter is mandatory.
35
 * Here is an example of how you should be able to use it:
36
 *     /**
37
 *      * @Route("/Blog")
38
 *      * /
39
 *     class Blog
40
 *     {
41
 *         /**
42
 *          * @Route("/", name="blog_index")
43
 *          * /
44
 *         public function index()
45
 *         {
46
 *         }
47
 *         /**
48
 *          * @Route("/{id}", name="blog_post", requirements = {"id" = "\d+"})
49
 *          * /
50
 *         public function show()
51
 *         {
52
 *         }
53
 *     }
54
 *
55
 * @author Fabien Potencier <fabien@symfony.com>
56
 */
57
abstract class AnnotationClassLoader implements LoaderInterface
58
{
59
    protected $reader;
60
 
61
    /**
62
     * @var string
63
     */
64
    protected $routeAnnotationClass = 'Symfony\\Component\\Routing\\Annotation\\Route';
65
 
66
    /**
67
     * @var int
68
     */
69
    protected $defaultRouteIndex = 0;
70
 
71
    public function __construct(Reader $reader)
72
    {
73
        $this->reader = $reader;
74
    }
75
 
76
    /**
77
     * Sets the annotation class to read route properties from.
78
     */
79
    public function setRouteAnnotationClass(string $class)
80
    {
81
        $this->routeAnnotationClass = $class;
82
    }
83
 
84
    /**
85
     * Loads from annotations from a class.
86
     *
87
     * @param string $class A class name
88
     *
89
     * @return RouteCollection A RouteCollection instance
90
     *
91
     * @throws \InvalidArgumentException When route can't be parsed
92
     */
93
    public function load($class, string $type = null)
94
    {
95
        if (!class_exists($class)) {
96
            throw new \InvalidArgumentException(sprintf('Class "%s" does not exist.', $class));
97
        }
98
 
99
        $class = new \ReflectionClass($class);
100
        if ($class->isAbstract()) {
101
            throw new \InvalidArgumentException(sprintf('Annotations from class "%s" cannot be read as it is abstract.', $class->getName()));
102
        }
103
 
104
        $globals = $this->getGlobals($class);
105
 
106
        $collection = new RouteCollection();
107
        $collection->addResource(new FileResource($class->getFileName()));
108
 
109
        foreach ($class->getMethods() as $method) {
110
            $this->defaultRouteIndex = 0;
111
            foreach ($this->reader->getMethodAnnotations($method) as $annot) {
112
                if ($annot instanceof $this->routeAnnotationClass) {
113
                    $this->addRoute($collection, $annot, $globals, $class, $method);
114
                }
115
            }
116
        }
117
 
118
        if (0 === $collection->count() && $class->hasMethod('__invoke')) {
119
            $globals = $this->resetGlobals();
120
            foreach ($this->reader->getClassAnnotations($class) as $annot) {
121
                if ($annot instanceof $this->routeAnnotationClass) {
122
                    $this->addRoute($collection, $annot, $globals, $class, $class->getMethod('__invoke'));
123
                }
124
            }
125
        }
126
 
127
        return $collection;
128
    }
129
 
130
    /**
131
     * @param RouteAnnotation $annot or an object that exposes a similar interface
132
     */
133
    protected function addRoute(RouteCollection $collection, $annot, array $globals, \ReflectionClass $class, \ReflectionMethod $method)
134
    {
135
        $name = $annot->getName();
136
        if (null === $name) {
137
            $name = $this->getDefaultRouteName($class, $method);
138
        }
139
        $name = $globals['name'].$name;
140
 
141
        $requirements = $annot->getRequirements();
142
 
143
        foreach ($requirements as $placeholder => $requirement) {
144
            if (\is_int($placeholder)) {
145
                throw new \InvalidArgumentException(sprintf('A placeholder name must be a string (%d given). Did you forget to specify the placeholder key for the requirement "%s" of route "%s" in "%s::%s()"?', $placeholder, $requirement, $name, $class->getName(), $method->getName()));
146
            }
147
        }
148
 
149
        $defaults = array_replace($globals['defaults'], $annot->getDefaults());
150
        $requirements = array_replace($globals['requirements'], $requirements);
151
        $options = array_replace($globals['options'], $annot->getOptions());
152
        $schemes = array_merge($globals['schemes'], $annot->getSchemes());
153
        $methods = array_merge($globals['methods'], $annot->getMethods());
154
 
155
        $host = $annot->getHost();
156
        if (null === $host) {
157
            $host = $globals['host'];
158
        }
159
 
160
        $condition = $annot->getCondition() ?? $globals['condition'];
161
        $priority = $annot->getPriority() ?? $globals['priority'];
162
 
163
        $path = $annot->getLocalizedPaths() ?: $annot->getPath();
164
        $prefix = $globals['localized_paths'] ?: $globals['path'];
165
        $paths = [];
166
 
167
        if (\is_array($path)) {
168
            if (!\is_array($prefix)) {
169
                foreach ($path as $locale => $localePath) {
170
                    $paths[$locale] = $prefix.$localePath;
171
                }
172
            } elseif ($missing = array_diff_key($prefix, $path)) {
173
                throw new \LogicException(sprintf('Route to "%s" is missing paths for locale(s) "%s".', $class->name.'::'.$method->name, implode('", "', array_keys($missing))));
174
            } else {
175
                foreach ($path as $locale => $localePath) {
176
                    if (!isset($prefix[$locale])) {
177
                        throw new \LogicException(sprintf('Route to "%s" with locale "%s" is missing a corresponding prefix in class "%s".', $method->name, $locale, $class->name));
178
                    }
179
 
180
                    $paths[$locale] = $prefix[$locale].$localePath;
181
                }
182
            }
183
        } elseif (\is_array($prefix)) {
184
            foreach ($prefix as $locale => $localePrefix) {
185
                $paths[$locale] = $localePrefix.$path;
186
            }
187
        } else {
188
            $paths[] = $prefix.$path;
189
        }
190
 
191
        foreach ($method->getParameters() as $param) {
192
            if (isset($defaults[$param->name]) || !$param->isDefaultValueAvailable()) {
193
                continue;
194
            }
195
            foreach ($paths as $locale => $path) {
196
                if (preg_match(sprintf('/\{%s(?:<.*?>)?\}/', preg_quote($param->name)), $path)) {
197
                    $defaults[$param->name] = $param->getDefaultValue();
198
                    break;
199
                }
200
            }
201
        }
202
 
203
        foreach ($paths as $locale => $path) {
204
            $route = $this->createRoute($path, $defaults, $requirements, $options, $host, $schemes, $methods, $condition);
205
            $this->configureRoute($route, $class, $method, $annot);
206
            if (0 !== $locale) {
207
                $route->setDefault('_locale', $locale);
208
                $route->setRequirement('_locale', preg_quote($locale));
209
                $route->setDefault('_canonical_route', $name);
210
                $collection->add($name.'.'.$locale, $route, $priority);
211
            } else {
212
                $collection->add($name, $route, $priority);
213
            }
214
        }
215
    }
216
 
217
    /**
218
     * {@inheritdoc}
219
     */
220
    public function supports($resource, string $type = null)
221
    {
222
        return \is_string($resource) && preg_match('/^(?:\\\\?[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)+$/', $resource) && (!$type || 'annotation' === $type);
223
    }
224
 
225
    /**
226
     * {@inheritdoc}
227
     */
228
    public function setResolver(LoaderResolverInterface $resolver)
229
    {
230
    }
231
 
232
    /**
233
     * {@inheritdoc}
234
     */
235
    public function getResolver()
236
    {
237
    }
238
 
239
    /**
240
     * Gets the default route name for a class method.
241
     *
242
     * @return string
243
     */
244
    protected function getDefaultRouteName(\ReflectionClass $class, \ReflectionMethod $method)
245
    {
246
        $name = str_replace('\\', '_', $class->name).'_'.$method->name;
247
        $name = \function_exists('mb_strtolower') && preg_match('//u', $name) ? mb_strtolower($name, 'UTF-8') : strtolower($name);
248
        if ($this->defaultRouteIndex > 0) {
249
            $name .= '_'.$this->defaultRouteIndex;
250
        }
251
        ++$this->defaultRouteIndex;
252
 
253
        return $name;
254
    }
255
 
256
    protected function getGlobals(\ReflectionClass $class)
257
    {
258
        $globals = $this->resetGlobals();
259
 
260
        if ($annot = $this->reader->getClassAnnotation($class, $this->routeAnnotationClass)) {
261
            if (null !== $annot->getName()) {
262
                $globals['name'] = $annot->getName();
263
            }
264
 
265
            if (null !== $annot->getPath()) {
266
                $globals['path'] = $annot->getPath();
267
            }
268
 
269
            $globals['localized_paths'] = $annot->getLocalizedPaths();
270
 
271
            if (null !== $annot->getRequirements()) {
272
                $globals['requirements'] = $annot->getRequirements();
273
            }
274
 
275
            if (null !== $annot->getOptions()) {
276
                $globals['options'] = $annot->getOptions();
277
            }
278
 
279
            if (null !== $annot->getDefaults()) {
280
                $globals['defaults'] = $annot->getDefaults();
281
            }
282
 
283
            if (null !== $annot->getSchemes()) {
284
                $globals['schemes'] = $annot->getSchemes();
285
            }
286
 
287
            if (null !== $annot->getMethods()) {
288
                $globals['methods'] = $annot->getMethods();
289
            }
290
 
291
            if (null !== $annot->getHost()) {
292
                $globals['host'] = $annot->getHost();
293
            }
294
 
295
            if (null !== $annot->getCondition()) {
296
                $globals['condition'] = $annot->getCondition();
297
            }
298
 
299
            $globals['priority'] = $annot->getPriority() ?? 0;
300
 
301
            foreach ($globals['requirements'] as $placeholder => $requirement) {
302
                if (\is_int($placeholder)) {
303
                    throw new \InvalidArgumentException(sprintf('A placeholder name must be a string (%d given). Did you forget to specify the placeholder key for the requirement "%s" in "%s"?', $placeholder, $requirement, $class->getName()));
304
                }
305
            }
306
        }
307
 
308
        return $globals;
309
    }
310
 
311
    private function resetGlobals(): array
312
    {
313
        return [
314
            'path' => null,
315
            'localized_paths' => [],
316
            'requirements' => [],
317
            'options' => [],
318
            'defaults' => [],
319
            'schemes' => [],
320
            'methods' => [],
321
            'host' => '',
322
            'condition' => '',
323
            'name' => '',
324
            'priority' => 0,
325
        ];
326
    }
327
 
328
    protected function createRoute(string $path, array $defaults, array $requirements, array $options, ?string $host, array $schemes, array $methods, ?string $condition)
329
    {
330
        return new Route($path, $defaults, $requirements, $options, $host, $schemes, $methods, $condition);
331
    }
332
 
333
    abstract protected function configureRoute(Route $route, \ReflectionClass $class, \ReflectionMethod $method, $annot);
334
}