11
11
12
12
namespace Symfony \Component \Routing \Loader ;
13
13
14
+ use Doctrine \Common \Annotations \Reader ;
14
15
use Symfony \Component \Config \Loader \LoaderInterface ;
15
16
use Symfony \Component \Config \Loader \LoaderResolverInterface ;
16
17
use Symfony \Component \Config \Resource \FileResource ;
19
20
use Symfony \Component \Routing \RouteCollection ;
20
21
21
22
/**
22
- * AnnotationClassLoader loads routing information from a PHP class and its methods.
23
+ * AttributeClassLoader loads routing information from a PHP class and its methods.
23
24
*
24
25
* You need to define an implementation for the configureRoute() method. Most of the
25
26
* time, this method should define some PHP callable to be called for the route
48
49
*
49
50
* @author Fabien Potencier <fabien@symfony.com>
50
51
* @author Alexander M. Turek <me@derrabus.de>
52
+ * @author Alexandre Daubois <alex.daubois@gmail.com>
51
53
*/
52
- abstract class AnnotationClassLoader implements LoaderInterface
54
+ abstract class AttributeClassLoader implements LoaderInterface
53
55
{
54
- protected string $ routeAnnotationClass = RouteAnnotation::class;
55
- protected int $ defaultRouteIndex = 0 ;
56
+ /**
57
+ * @var Reader|null
58
+ *
59
+ * @deprecated in Symfony 6.4, this property will be removed in Symfony 7.
60
+ */
61
+ protected $ reader ;
62
+
63
+ /**
64
+ * @var string|null
65
+ */
66
+ protected $ env ;
67
+
68
+ /**
69
+ * @var string
70
+ */
71
+ protected $ routeAnnotationClass = RouteAnnotation::class;
56
72
57
- public function __construct (
58
- protected readonly ?string $ env = null ,
59
- ) {
73
+ /**
74
+ * @var int
75
+ */
76
+ protected $ defaultRouteIndex = 0 ;
77
+
78
+ private bool $ hasDeprecatedAnnotations = false ;
79
+
80
+ /**
81
+ * @param string|null $env
82
+ */
83
+ public function __construct ($ env = null )
84
+ {
85
+ if ($ env instanceof Reader || null === $ env && \func_num_args () > 1 && null !== func_get_arg (1 )) {
86
+ trigger_deprecation ('symfony/routing ' , '6.4 ' , 'Passing an instance of "%s" as first and the environment as second argument to "%s" is deprecated. Pass the environment as first argument instead. ' , Reader::class, __METHOD__ );
87
+
88
+ $ this ->reader = $ env ;
89
+ $ env = \func_num_args () > 1 ? func_get_arg (1 ) : null ;
90
+ }
91
+
92
+ if (\is_string ($ env ) || null === $ env ) {
93
+ $ this ->env = $ env ;
94
+ } elseif ($ env instanceof \Stringable || \is_scalar ($ env )) {
95
+ $ this ->env = (string ) $ env ;
96
+ } else {
97
+ throw new \TypeError (__METHOD__ .sprintf (': Parameter $env was expected to be a string or null, "%s" given. ' , get_debug_type ($ env )));
98
+ }
60
99
}
61
100
62
101
/**
63
102
* Sets the annotation class to read route properties from.
103
+ *
104
+ * @return void
64
105
*/
65
- public function setRouteAnnotationClass (string $ class ): void
106
+ public function setRouteAnnotationClass (string $ class )
66
107
{
67
108
$ this ->routeAnnotationClass = $ class ;
68
109
}
@@ -83,47 +124,59 @@ public function load(mixed $class, string $type = null): RouteCollection
83
124
throw new \InvalidArgumentException (sprintf ('Annotations from class "%s" cannot be read as it is abstract. ' , $ class ->getName ()));
84
125
}
85
126
86
- $ globals = $ this ->getGlobals ($ class );
87
- $ collection = new RouteCollection ();
88
- $ collection ->addResource (new FileResource ($ class ->getFileName ()));
89
- if ($ globals ['env ' ] && $ this ->env !== $ globals ['env ' ]) {
90
- return $ collection ;
91
- }
92
- $ fqcnAlias = false ;
93
- foreach ($ class ->getMethods () as $ method ) {
94
- $ this ->defaultRouteIndex = 0 ;
95
- $ routeNamesBefore = array_keys ($ collection ->all ());
96
- foreach ($ this ->getAnnotations ($ method ) as $ annot ) {
97
- $ this ->addRoute ($ collection , $ annot , $ globals , $ class , $ method );
98
- if ('__invoke ' === $ method ->name ) {
127
+ $ this ->hasDeprecatedAnnotations = false ;
128
+
129
+ try {
130
+ $ globals = $ this ->getGlobals ($ class );
131
+ $ collection = new RouteCollection ();
132
+ $ collection ->addResource (new FileResource ($ class ->getFileName ()));
133
+ if ($ globals ['env ' ] && $ this ->env !== $ globals ['env ' ]) {
134
+ return $ collection ;
135
+ }
136
+ $ fqcnAlias = false ;
137
+ foreach ($ class ->getMethods () as $ method ) {
138
+ $ this ->defaultRouteIndex = 0 ;
139
+ $ routeNamesBefore = array_keys ($ collection ->all ());
140
+ foreach ($ this ->getAnnotations ($ method ) as $ annot ) {
141
+ $ this ->addRoute ($ collection , $ annot , $ globals , $ class , $ method );
142
+ if ('__invoke ' === $ method ->name ) {
143
+ $ fqcnAlias = true ;
144
+ }
145
+ }
146
+
147
+ if (1 === $ collection ->count () - \count ($ routeNamesBefore )) {
148
+ $ newRouteName = current (array_diff (array_keys ($ collection ->all ()), $ routeNamesBefore ));
149
+ $ collection ->addAlias (sprintf ('%s::%s ' , $ class ->name , $ method ->name ), $ newRouteName );
150
+ }
151
+ }
152
+ if (0 === $ collection ->count () && $ class ->hasMethod ('__invoke ' )) {
153
+ $ globals = $ this ->resetGlobals ();
154
+ foreach ($ this ->getAnnotations ($ class ) as $ annot ) {
155
+ $ this ->addRoute ($ collection , $ annot , $ globals , $ class , $ class ->getMethod ('__invoke ' ));
99
156
$ fqcnAlias = true ;
100
157
}
101
158
}
102
-
103
- if (1 === $ collection ->count () - \count ($ routeNamesBefore )) {
104
- $ newRouteName = current (array_diff (array_keys ($ collection ->all ()), $ routeNamesBefore ));
105
- $ collection ->addAlias (sprintf ('%s::%s ' , $ class ->name , $ method ->name ), $ newRouteName );
159
+ if ($ fqcnAlias && 1 === $ collection ->count ()) {
160
+ $ collection ->addAlias ($ class ->name , $ invokeRouteName = key ($ collection ->all ()));
161
+ $ collection ->addAlias (sprintf ('%s::__invoke ' , $ class ->name ), $ invokeRouteName );
106
162
}
107
- }
108
- if (0 === $ collection ->count () && $ class ->hasMethod ('__invoke ' )) {
109
- $ globals = $ this ->resetGlobals ();
110
- foreach ($ this ->getAnnotations ($ class ) as $ annot ) {
111
- $ this ->addRoute ($ collection , $ annot , $ globals , $ class , $ class ->getMethod ('__invoke ' ));
112
- $ fqcnAlias = true ;
163
+
164
+ if ($ this ->hasDeprecatedAnnotations ) {
165
+ trigger_deprecation ('symfony/routing ' , '6.4 ' , 'Class "%s" uses Doctrine Annotations to configure routes, which is deprecated. Use PHP attributes instead. ' , $ class ->getName ());
113
166
}
114
- }
115
- if ($ fqcnAlias && 1 === $ collection ->count ()) {
116
- $ collection ->addAlias ($ class ->name , $ invokeRouteName = key ($ collection ->all ()));
117
- $ collection ->addAlias (sprintf ('%s::__invoke ' , $ class ->name ), $ invokeRouteName );
167
+ } finally {
168
+ $ this ->hasDeprecatedAnnotations = false ;
118
169
}
119
170
120
171
return $ collection ;
121
172
}
122
173
123
174
/**
124
175
* @param RouteAnnotation $annot or an object that exposes a similar interface
176
+ *
177
+ * @return void
125
178
*/
126
- protected function addRoute (RouteCollection $ collection , object $ annot , array $ globals , \ReflectionClass $ class , \ReflectionMethod $ method ): void
179
+ protected function addRoute (RouteCollection $ collection , object $ annot , array $ globals , \ReflectionClass $ class , \ReflectionMethod $ method )
127
180
{
128
181
if ($ annot ->getEnv () && $ annot ->getEnv () !== $ this ->env ) {
129
182
return ;
@@ -210,6 +263,10 @@ protected function addRoute(RouteCollection $collection, object $annot, array $g
210
263
211
264
public function supports (mixed $ resource , string $ type = null ): bool
212
265
{
266
+ if ('annotation ' === $ type ) {
267
+ trigger_deprecation ('symfony/routing ' , '6.4 ' , 'The "annotation" route type is deprecated, use the "attribute" route type instead. ' );
268
+ }
269
+
213
270
return \is_string ($ resource ) && preg_match ('/^(?: \\\\?[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)+$/ ' , $ resource ) && (!$ type || \in_array ($ type , ['annotation ' , 'attribute ' ], true ));
214
271
}
215
272
@@ -223,8 +280,10 @@ public function getResolver(): LoaderResolverInterface
223
280
224
281
/**
225
282
* Gets the default route name for a class method.
283
+ *
284
+ * @return string
226
285
*/
227
- protected function getDefaultRouteName (\ReflectionClass $ class , \ReflectionMethod $ method ): string
286
+ protected function getDefaultRouteName (\ReflectionClass $ class , \ReflectionMethod $ method )
228
287
{
229
288
$ name = str_replace ('\\' , '_ ' , $ class ->name ).'_ ' .$ method ->name ;
230
289
$ name = \function_exists ('mb_strtolower ' ) && preg_match ('//u ' , $ name ) ? mb_strtolower ($ name , 'UTF-8 ' ) : strtolower ($ name );
@@ -239,13 +298,19 @@ protected function getDefaultRouteName(\ReflectionClass $class, \ReflectionMetho
239
298
/**
240
299
* @return array<string, mixed>
241
300
*/
242
- protected function getGlobals (\ReflectionClass $ class ): array
301
+ protected function getGlobals (\ReflectionClass $ class )
243
302
{
244
303
$ globals = $ this ->resetGlobals ();
245
304
305
+ $ annot = null ;
246
306
if ($ attribute = $ class ->getAttributes ($ this ->routeAnnotationClass , \ReflectionAttribute::IS_INSTANCEOF )[0 ] ?? null ) {
247
307
$ annot = $ attribute ->newInstance ();
308
+ }
309
+ if (!$ annot && $ annot = $ this ->reader ?->getClassAnnotation($ class , $ this ->routeAnnotationClass )) {
310
+ $ this ->hasDeprecatedAnnotations = true ;
311
+ }
248
312
313
+ if ($ annot ) {
249
314
if (null !== $ annot ->getName ()) {
250
315
$ globals ['name ' ] = $ annot ->getName ();
251
316
}
@@ -315,7 +380,10 @@ private function resetGlobals(): array
315
380
];
316
381
}
317
382
318
- protected function createRoute (string $ path , array $ defaults , array $ requirements , array $ options , ?string $ host , array $ schemes , array $ methods , ?string $ condition ): Route
383
+ /**
384
+ * @return Route
385
+ */
386
+ protected function createRoute (string $ path , array $ defaults , array $ requirements , array $ options , ?string $ host , array $ schemes , array $ methods , ?string $ condition )
319
387
{
320
388
return new Route ($ path , $ defaults , $ requirements , $ options , $ host , $ schemes , $ methods , $ condition );
321
389
}
@@ -333,5 +401,25 @@ private function getAnnotations(\ReflectionClass|\ReflectionMethod $reflection):
333
401
foreach ($ reflection ->getAttributes ($ this ->routeAnnotationClass , \ReflectionAttribute::IS_INSTANCEOF ) as $ attribute ) {
334
402
yield $ attribute ->newInstance ();
335
403
}
404
+
405
+ if (!$ this ->reader ) {
406
+ return ;
407
+ }
408
+
409
+ $ annotations = $ reflection instanceof \ReflectionClass
410
+ ? $ this ->reader ->getClassAnnotations ($ reflection )
411
+ : $ this ->reader ->getMethodAnnotations ($ reflection );
412
+
413
+ foreach ($ annotations as $ annotation ) {
414
+ if ($ annotation instanceof $ this ->routeAnnotationClass ) {
415
+ $ this ->hasDeprecatedAnnotations = true ;
416
+
417
+ yield $ annotation ;
418
+ }
419
+ }
336
420
}
337
421
}
422
+
423
+ if (!class_exists (AnnotationClassLoader::class, false )) {
424
+ class_alias (AttributeClassLoader::class, AnnotationClassLoader::class);
425
+ }
0 commit comments