feature #40266 [Routing] Construct Route annotations using named arguments (derrabus)

This PR was merged into the 5.3-dev branch.

Discussion
----------

[Routing] Construct Route annotations using named arguments

| Q             | A
| ------------- | ---
| Branch?       | 5.x
| Bug fix?      | no
| New feature?  | no
| Deprecations? | yes
| Tickets       | N/A
| License       | MIT
| Doc PR        | Not needed

This PR proposes to bump the `doctrine/annotations` library to 1.12 to gain access to its emulation layer for named arguments. Furthermore, constructing a `Route` annotation the old way by passing an array of parameters is deprecated.

### Reasons for this change

The constructors of our annotation classes have become unnecessarily complicated because we have to support two ways of calling them:
* An array of parameters, passed as first argument, because that's the default behavior `doctrine/annotations`.
* A set of named arguments because that's how PHP 8 attributes work.

Since we can now tell the Doctrine annotation reader to use named arguments as well, we can simplify the constructors of our annotations significantly.

### Drawback

After this change, there is no easy way anymore to construct instances of the `Route` annotation class directly on PHP 7. The PR has been built under the assumption that instances of this class are usually created using either Doctrine annotations or a PHP 8 attribute. Thus, most applications should be unaffected by this change.

Commits
-------

29b0f96046 [Routing] Construct Route annotations using named arguments
This commit is contained in:
Fabien Potencier 2021-02-25 08:31:04 +01:00
commit d54a1223f7
8 changed files with 47 additions and 7 deletions

View File

@ -69,6 +69,11 @@ PropertyInfo
* Deprecated the `Type::getCollectionKeyType()` and `Type::getCollectionValueType()` methods, use `Type::getCollectionKeyTypes()` and `Type::getCollectionValueTypes()` instead
Routing
-------
* Deprecated creating instances of the `Route` annotation class by passing an array of parameters, use named arguments instead
Security
--------

View File

@ -164,6 +164,7 @@ Routing
* Removed `RouteCollectionBuilder`.
* Added argument `$priority` to `RouteCollection::add()`
* Removed the `RouteCompiler::REGEX_DELIMITER` constant
* Removed the `$data` parameter from the constructor of the `Route` annotation class
Security
--------

View File

@ -123,7 +123,7 @@
"async-aws/sqs": "^1.0",
"cache/integration-tests": "dev-master",
"composer/package-versions-deprecated": "^1.8",
"doctrine/annotations": "^1.10.4",
"doctrine/annotations": "^1.12",
"doctrine/cache": "~1.6",
"doctrine/collections": "~1.0",
"doctrine/data-fixtures": "^1.1",
@ -151,6 +151,7 @@
},
"conflict": {
"async-aws/core": "<1.5",
"doctrine/annotations": "<1.12",
"doctrine/dbal": "<2.10",
"masterminds/html5": "<2.6",
"phpdocumentor/reflection-docblock": "<3.2.2",

View File

@ -15,6 +15,7 @@ namespace Symfony\Component\Routing\Annotation;
* Annotation class for @Route().
*
* @Annotation
* @NamedArgumentConstructor
* @Target({"CLASS", "METHOD"})
*
* @author Fabien Potencier <fabien@symfony.com>
@ -67,6 +68,8 @@ class Route
$data = ['path' => $data];
} elseif (!\is_array($data)) {
throw new \TypeError(sprintf('"%s": Argument $data is expected to be a string or array, got "%s".', __METHOD__, get_debug_type($data)));
} elseif ([] !== $data) {
trigger_deprecation('symfony/routing', '5.3', 'Passing an array as first argument to "%s" is deprecated. Use named arguments instead.', __METHOD__);
}
if (null !== $path && !\is_string($path) && !\is_array($path)) {
throw new \TypeError(sprintf('"%s": Argument $path is expected to be a string, array or null, got "%s".', __METHOD__, get_debug_type($path)));

View File

@ -6,6 +6,7 @@ CHANGELOG
* Already encoded slashes are not decoded nor double-encoded anymore when generating URLs
* Add support for per-env configuration in loaders
* Deprecate creating instances of the `Route` annotation class by passing an array of parameters
5.2.0
-----

View File

@ -12,16 +12,25 @@
namespace Symfony\Component\Routing\Tests\Annotation;
use PHPUnit\Framework\TestCase;
use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait;
use Symfony\Component\Routing\Annotation\Route;
class RouteTest extends TestCase
{
use ExpectDeprecationTrait;
/**
* @group legacy
*/
public function testInvalidRouteParameter()
{
$this->expectException(\BadMethodCallException::class);
new Route(['foo' => 'bar']);
}
/**
* @group legacy
*/
public function testTryingToSetLocalesDirectly()
{
$this->expectException(\BadMethodCallException::class);
@ -29,18 +38,30 @@ class RouteTest extends TestCase
}
/**
* @requires PHP 8
* @dataProvider getValidParameters
*/
public function testRouteParameters($parameter, $value, $getter)
public function testRouteParameters(string $parameter, $value, string $getter)
{
$route = new Route(...[$parameter => $value]);
$this->assertEquals($route->$getter(), $value);
}
/**
* @group legacy
* @dataProvider getLegacyValidParameters
*/
public function testLegacyRouteParameters(string $parameter, $value, string $getter)
{
$this->expectDeprecation('Since symfony/routing 5.3: Passing an array as first argument to "Symfony\Component\Routing\Annotation\Route::__construct" is deprecated. Use named arguments instead.');
$route = new Route([$parameter => $value]);
$this->assertEquals($route->$getter(), $value);
}
public function getValidParameters()
public function getValidParameters(): iterable
{
return [
['value', '/Blog', 'getPath'],
['requirements', ['locale' => 'en'], 'getRequirements'],
['options', ['compiler_class' => 'RouteCompiler'], 'getOptions'],
['name', 'blog_index', 'getName'],
@ -49,7 +70,14 @@ class RouteTest extends TestCase
['methods', ['GET', 'POST'], 'getMethods'],
['host', '{locale}.example.com', 'getHost'],
['condition', 'context.getMethod() == "GET"', 'getCondition'],
['value', ['nl' => '/hier', 'en' => '/here'], 'getLocalizedPaths'],
];
}
public function getLegacyValidParameters(): iterable
{
yield from $this->getValidParameters();
yield ['value', '/Blog', 'getPath'];
yield ['value', ['nl' => '/hier', 'en' => '/here'], 'getLocalizedPaths'];
}
}

View File

@ -51,7 +51,7 @@ class AnnotationFileLoaderTest extends AbstractAnnotationLoaderTest
public function testLoadVariadic()
{
$route = new Route(['path' => '/path/to/{id}']);
$route = new Route('/path/to/{id}');
$this->reader->expects($this->once())->method('getClassAnnotation');
$this->reader->expects($this->once())->method('getMethodAnnotations')
->willReturn([$route]);

View File

@ -26,10 +26,11 @@
"symfony/yaml": "^4.4|^5.0",
"symfony/expression-language": "^4.4|^5.0",
"symfony/dependency-injection": "^4.4|^5.0",
"doctrine/annotations": "^1.10.4",
"doctrine/annotations": "^1.12",
"psr/log": "~1.0"
},
"conflict": {
"doctrine/annotations": "<1.12",
"symfony/config": "<5.3",
"symfony/dependency-injection": "<4.4",
"symfony/yaml": "<4.4"