Skip to content

Commit 4361dad

Browse files
[Routing] add priority option to annotated routes
1 parent 0a09965 commit 4361dad

File tree

7 files changed

+95
-15
lines changed

7 files changed

+95
-15
lines changed

Annotation/Route.php

+11
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ class Route
3434
private $locale;
3535
private $format;
3636
private $utf8;
37+
private $priority;
3738

3839
/**
3940
* @param array $data An array of key/value parameters
@@ -179,4 +180,14 @@ public function getCondition()
179180
{
180181
return $this->condition;
181182
}
183+
184+
public function setPriority(int $priority): void
185+
{
186+
$this->priority = $priority;
187+
}
188+
189+
public function getPriority(): ?int
190+
{
191+
return $this->priority;
192+
}
182193
}

CHANGELOG.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ CHANGELOG
44
5.1.0
55
-----
66

7-
* Deprecated `RouteCollectionBuilder` in favor of `RoutingConfigurator`.
7+
* deprecated `RouteCollectionBuilder` in favor of `RoutingConfigurator`.
8+
* added "priority" option to annotated routes
9+
* added argument `$priority` to `RouteCollection::add()`
810

911
5.0.0
1012
-----

Loader/AnnotationClassLoader.php

+7-6
Original file line numberDiff line numberDiff line change
@@ -157,10 +157,8 @@ protected function addRoute(RouteCollection $collection, $annot, array $globals,
157157
$host = $globals['host'];
158158
}
159159

160-
$condition = $annot->getCondition();
161-
if (null === $condition) {
162-
$condition = $globals['condition'];
163-
}
160+
$condition = $annot->getCondition() ?? $globals['condition'];
161+
$priority = $annot->getPriority() ?? $globals['priority'];
164162

165163
$path = $annot->getLocalizedPaths() ?: $annot->getPath();
166164
$prefix = $globals['localized_paths'] ?: $globals['path'];
@@ -208,9 +206,9 @@ protected function addRoute(RouteCollection $collection, $annot, array $globals,
208206
if (0 !== $locale) {
209207
$route->setDefault('_locale', $locale);
210208
$route->setDefault('_canonical_route', $name);
211-
$collection->add($name.'.'.$locale, $route);
209+
$collection->add($name.'.'.$locale, $route, $priority);
212210
} else {
213-
$collection->add($name, $route);
211+
$collection->add($name, $route, $priority);
214212
}
215213
}
216214
}
@@ -297,6 +295,8 @@ protected function getGlobals(\ReflectionClass $class)
297295
$globals['condition'] = $annot->getCondition();
298296
}
299297

298+
$globals['priority'] = $annot->getPriority() ?? 0;
299+
300300
foreach ($globals['requirements'] as $placeholder => $requirement) {
301301
if (\is_int($placeholder)) {
302302
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()));
@@ -320,6 +320,7 @@ private function resetGlobals(): array
320320
'host' => '',
321321
'condition' => '',
322322
'name' => '',
323+
'priority' => 0,
323324
];
324325
}
325326

RouteCollection.php

+38-5
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@ class RouteCollection implements \IteratorAggregate, \Countable
3535
*/
3636
private $resources = [];
3737

38+
/**
39+
* @var int[]
40+
*/
41+
private $priorities = [];
42+
3843
public function __clone()
3944
{
4045
foreach ($this->routes as $name => $route) {
@@ -53,7 +58,7 @@ public function __clone()
5358
*/
5459
public function getIterator()
5560
{
56-
return new \ArrayIterator($this->routes);
61+
return new \ArrayIterator($this->all());
5762
}
5863

5964
/**
@@ -66,11 +71,22 @@ public function count()
6671
return \count($this->routes);
6772
}
6873

69-
public function add(string $name, Route $route)
74+
/**
75+
* @param int $priority
76+
*/
77+
public function add(string $name, Route $route/*, int $priority = 0*/)
7078
{
71-
unset($this->routes[$name]);
79+
if (\func_num_args() < 3 && __CLASS__ !== static::class && __CLASS__ !== (new \ReflectionMethod($this, __FUNCTION__))->getDeclaringClass()->getName() && !$this instanceof \PHPUnit\Framework\MockObject\MockObject && !$this instanceof \Prophecy\Prophecy\ProphecySubjectInterface) {
80+
@trigger_error(sprintf('The "%s()" method will have a new "int $priority = 0" argument in version 6.0, not defining it is deprecated since Symfony 5.1.', __METHOD__), E_USER_DEPRECATED);
81+
}
82+
83+
unset($this->routes[$name], $this->priorities[$name]);
7284

7385
$this->routes[$name] = $route;
86+
87+
if ($priority = 3 <= \func_num_args() ? func_get_arg(2) : 0) {
88+
$this->priorities[$name] = $priority;
89+
}
7490
}
7591

7692
/**
@@ -80,6 +96,14 @@ public function add(string $name, Route $route)
8096
*/
8197
public function all()
8298
{
99+
if ($this->priorities) {
100+
$priorities = $this->priorities;
101+
$keysOrder = array_flip(array_keys($this->routes));
102+
uksort($this->routes, static function ($n1, $n2) use ($priorities, $keysOrder) {
103+
return (($priorities[$n2] ?? 0) <=> ($priorities[$n1] ?? 0)) ?: ($keysOrder[$n1] <=> $keysOrder[$n2]);
104+
});
105+
}
106+
83107
return $this->routes;
84108
}
85109

@@ -101,7 +125,7 @@ public function get(string $name)
101125
public function remove($name)
102126
{
103127
foreach ((array) $name as $n) {
104-
unset($this->routes[$n]);
128+
unset($this->routes[$n], $this->priorities[$n]);
105129
}
106130
}
107131

@@ -114,8 +138,12 @@ public function addCollection(self $collection)
114138
// we need to remove all routes with the same names first because just replacing them
115139
// would not place the new route at the end of the merged array
116140
foreach ($collection->all() as $name => $route) {
117-
unset($this->routes[$name]);
141+
unset($this->routes[$name], $this->priorities[$name]);
118142
$this->routes[$name] = $route;
143+
144+
if (isset($collection->priorities[$name])) {
145+
$this->priorities[$name] = $collection->priorities[$name];
146+
}
119147
}
120148

121149
foreach ($collection->getResources() as $resource) {
@@ -147,15 +175,20 @@ public function addPrefix(string $prefix, array $defaults = [], array $requireme
147175
public function addNamePrefix(string $prefix)
148176
{
149177
$prefixedRoutes = [];
178+
$prefixedPriorities = [];
150179

151180
foreach ($this->routes as $name => $route) {
152181
$prefixedRoutes[$prefix.$name] = $route;
153182
if (null !== $name = $route->getDefault('_canonical_route')) {
154183
$route->setDefault('_canonical_route', $prefix.$name);
155184
}
185+
if (isset($this->priorities[$name])) {
186+
$prefixedPriorities[$prefix.$name] = $this->priorities[$name];
187+
}
156188
}
157189

158190
$this->routes = $prefixedRoutes;
191+
$this->priorities = $prefixedPriorities;
159192
}
160193

161194
/**

Tests/Fixtures/AnnotationFixtures/MethodActionControllers.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ public function post()
1717
}
1818

1919
/**
20-
* @Route(name="put", methods={"PUT"})
20+
* @Route(name="put", methods={"PUT"}, priority=10)
2121
*/
2222
public function put()
2323
{

Tests/Loader/AnnotationClassLoaderTest.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ public function testDefaultValuesForMethods()
144144
public function testMethodActionControllers()
145145
{
146146
$routes = $this->loader->load(MethodActionControllers::class);
147-
$this->assertCount(2, $routes);
147+
$this->assertSame(['put', 'post'], array_keys($routes->all()));
148148
$this->assertEquals('/the/path', $routes->get('put')->getPath());
149149
$this->assertEquals('/the/path', $routes->get('post')->getPath());
150150
}
@@ -178,7 +178,7 @@ public function testGlobalDefaultsRoutesLoadWithAnnotation()
178178
public function testUtf8RoutesLoadWithAnnotation()
179179
{
180180
$routes = $this->loader->load(Utf8ActionControllers::class);
181-
$this->assertCount(2, $routes);
181+
$this->assertSame(['one', 'two'], array_keys($routes->all()));
182182
$this->assertTrue($routes->get('one')->getOption('utf8'), 'The route must accept utf8');
183183
$this->assertFalse($routes->get('two')->getOption('utf8'), 'The route must not accept utf8');
184184
}

Tests/RouteCollectionTest.php

+33
Original file line numberDiff line numberDiff line change
@@ -330,4 +330,37 @@ public function testAddNamePrefixCanonicalRouteName()
330330
$this->assertEquals('api_bar', $collection->get('api_bar')->getDefault('_canonical_route'));
331331
$this->assertEquals('api_api_foo', $collection->get('api_api_foo')->getDefault('_canonical_route'));
332332
}
333+
334+
public function testAddWithPriority()
335+
{
336+
$collection = new RouteCollection();
337+
$collection->add('foo', $foo = new Route('/foo'), 0);
338+
$collection->add('bar', $bar = new Route('/bar'), 1);
339+
$collection->add('baz', $baz = new Route('/baz'));
340+
341+
$expected = [
342+
'bar' => $bar,
343+
'foo' => $foo,
344+
'baz' => $baz,
345+
];
346+
347+
$this->assertSame($expected, $collection->all());
348+
349+
$collection2 = new RouteCollection();
350+
$collection2->add('foo2', $foo2 = new Route('/foo'), 0);
351+
$collection2->add('bar2', $bar2 = new Route('/bar'), 1);
352+
$collection2->add('baz2', $baz2 = new Route('/baz'));
353+
$collection2->addCollection($collection);
354+
355+
$expected = [
356+
'bar2' => $bar2,
357+
'bar' => $bar,
358+
'foo2' => $foo2,
359+
'baz2' => $baz2,
360+
'foo' => $foo,
361+
'baz' => $baz,
362+
];
363+
364+
$this->assertSame($expected, $collection2->all());
365+
}
333366
}

0 commit comments

Comments
 (0)