feature #37545 [DependencyInjection] Add the Required attribute (derrabus)
This PR was merged into the 5.2-dev branch.
Discussion
----------
[DependencyInjection] Add the Required attribute
| Q | A
| ------------- | ---
| Branch? | master
| Bug fix? | no
| New feature? | yes
| Deprecations? | no
| Tickets | N/A
| License | MIT
| Doc PR | TODO
This PR proposes a new attribute `#[Required]` that can be used instead of the `@required` annotation.
Commits
-------
ea262441e7
[DependencyInjection] Add the Required attribute.
This commit is contained in:
commit
b26f11ca0b
1
.github/patch-types.php
vendored
1
.github/patch-types.php
vendored
@ -22,6 +22,7 @@ foreach ($loader->getClassMap() as $class => $file) {
|
||||
case false !== strpos($file, '/src/Symfony/Component/Debug/Tests/Fixtures/'):
|
||||
case false !== strpos($file, '/src/Symfony/Component/DependencyInjection/Tests/Compiler/OptionalServiceClass.php'):
|
||||
case false !== strpos($file, '/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/autowiring_classes.php'):
|
||||
case false !== strpos($file, '/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/autowiring_classes_80.php'):
|
||||
case false !== strpos($file, '/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/uniontype_classes.php'):
|
||||
case false !== strpos($file, '/src/Symfony/Component/DependencyInjection/Tests/Fixtures/ParentNotExists.php'):
|
||||
case false !== strpos($file, '/src/Symfony/Component/DependencyInjection/Tests/Fixtures/Prototype/BadClasses/MissingParent.php'):
|
||||
|
@ -12,6 +12,7 @@
|
||||
namespace Symfony\Component\DependencyInjection\Compiler;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Definition;
|
||||
use Symfony\Contracts\Service\Attribute\Required;
|
||||
|
||||
/**
|
||||
* Looks for definitions with autowiring enabled and registers their corresponding "@required" methods as setters.
|
||||
@ -49,6 +50,14 @@ class AutowireRequiredMethodsPass extends AbstractRecursivePass
|
||||
}
|
||||
|
||||
while (true) {
|
||||
if (\PHP_VERSION_ID >= 80000 && $r->getAttributes(Required::class)) {
|
||||
if ($this->isWither($r, $r->getDocComment() ?: '')) {
|
||||
$withers[] = [$r->name, [], true];
|
||||
} else {
|
||||
$value->addMethodCall($r->name, []);
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (false !== $doc = $r->getDocComment()) {
|
||||
if (false !== stripos($doc, '@required') && preg_match('#(?:^/\*\*|\n\s*+\*)\s*+@required(?:\s|\*/$)#i', $doc)) {
|
||||
if ($this->isWither($reflectionMethod, $doc)) {
|
||||
|
@ -14,6 +14,7 @@ namespace Symfony\Component\DependencyInjection\Compiler;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\DependencyInjection\Definition;
|
||||
use Symfony\Component\DependencyInjection\TypedReference;
|
||||
use Symfony\Contracts\Service\Attribute\Required;
|
||||
|
||||
/**
|
||||
* Looks for definitions with autowiring enabled and registers their corresponding "@required" properties.
|
||||
@ -45,10 +46,9 @@ class AutowireRequiredPropertiesPass extends AbstractRecursivePass
|
||||
if (!($type = $reflectionProperty->getType()) instanceof \ReflectionNamedType) {
|
||||
continue;
|
||||
}
|
||||
if (false === $doc = $reflectionProperty->getDocComment()) {
|
||||
continue;
|
||||
}
|
||||
if (false === stripos($doc, '@required') || !preg_match('#(?:^/\*\*|\n\s*+\*)\s*+@required(?:\s|\*/$)#i', $doc)) {
|
||||
if ((\PHP_VERSION_ID < 80000 || !$reflectionProperty->getAttributes(Required::class))
|
||||
&& ((false === $doc = $reflectionProperty->getDocComment()) || false === stripos($doc, '@required') || !preg_match('#(?:^/\*\*|\n\s*+\*)\s*+@required(?:\s|\*/$)#i', $doc))
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
if (\array_key_exists($name = $reflectionProperty->getName(), $properties)) {
|
||||
|
@ -28,6 +28,7 @@ use Symfony\Component\DependencyInjection\Tests\Fixtures\CaseSensitiveClass;
|
||||
use Symfony\Component\DependencyInjection\Tests\Fixtures\includes\FooVariadic;
|
||||
use Symfony\Component\DependencyInjection\Tests\Fixtures\includes\MultipleArgumentsOptionalScalarNotReallyOptional;
|
||||
use Symfony\Component\DependencyInjection\TypedReference;
|
||||
use Symfony\Contracts\Service\Attribute\Required;
|
||||
|
||||
require_once __DIR__.'/../Fixtures/includes/autowiring_classes.php';
|
||||
|
||||
@ -640,6 +641,32 @@ class AutowirePassTest extends TestCase
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @requires PHP 8
|
||||
*/
|
||||
public function testSetterInjectionWithAttribute()
|
||||
{
|
||||
if (!class_exists(Required::class)) {
|
||||
$this->markTestSkipped('symfony/service-contracts 2.2 required');
|
||||
}
|
||||
|
||||
$container = new ContainerBuilder();
|
||||
$container->register(Foo::class);
|
||||
|
||||
$container
|
||||
->register('setter_injection', AutowireSetter::class)
|
||||
->setAutowired(true);
|
||||
|
||||
(new ResolveClassPass())->process($container);
|
||||
(new AutowireRequiredMethodsPass())->process($container);
|
||||
(new AutowirePass())->process($container);
|
||||
|
||||
$methodCalls = $container->getDefinition('setter_injection')->getMethodCalls();
|
||||
$this->assertCount(1, $methodCalls);
|
||||
$this->assertSame('setFoo', $methodCalls[0][0]);
|
||||
$this->assertSame(Foo::class, (string) $methodCalls[0][1][0]);
|
||||
}
|
||||
|
||||
public function testWithNonExistingSetterAndAutowiring()
|
||||
{
|
||||
$this->expectException(RuntimeException::class);
|
||||
|
@ -16,6 +16,7 @@ use Symfony\Component\DependencyInjection\Compiler\AutowireRequiredMethodsPass;
|
||||
use Symfony\Component\DependencyInjection\Compiler\ResolveClassPass;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Tests\Fixtures\WitherStaticReturnType;
|
||||
use Symfony\Contracts\Service\Attribute\Required;
|
||||
|
||||
require_once __DIR__.'/../Fixtures/includes/autowiring_classes.php';
|
||||
|
||||
@ -54,6 +55,29 @@ class AutowireRequiredMethodsPassTest extends TestCase
|
||||
$this->assertEquals([], $methodCalls[1][1]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @requires PHP 8
|
||||
*/
|
||||
public function testSetterInjectionWithAttribute()
|
||||
{
|
||||
if (!class_exists(Required::class)) {
|
||||
$this->markTestSkipped('symfony/service-contracts 2.2 required');
|
||||
}
|
||||
|
||||
$container = new ContainerBuilder();
|
||||
$container->register(Foo::class);
|
||||
|
||||
$container
|
||||
->register('setter_injection', AutowireSetter::class)
|
||||
->setAutowired(true);
|
||||
|
||||
(new ResolveClassPass())->process($container);
|
||||
(new AutowireRequiredMethodsPass())->process($container);
|
||||
|
||||
$methodCalls = $container->getDefinition('setter_injection')->getMethodCalls();
|
||||
$this->assertSame([['setFoo', []]], $methodCalls);
|
||||
}
|
||||
|
||||
public function testExplicitMethodInjection()
|
||||
{
|
||||
$container = new ContainerBuilder();
|
||||
@ -124,4 +148,26 @@ class AutowireRequiredMethodsPassTest extends TestCase
|
||||
];
|
||||
$this->assertSame($expected, $methodCalls);
|
||||
}
|
||||
|
||||
/**
|
||||
* @requires PHP 8
|
||||
*/
|
||||
public function testWitherInjectionWithAttribute()
|
||||
{
|
||||
if (!class_exists(Required::class)) {
|
||||
$this->markTestSkipped('symfony/service-contracts 2.2 required');
|
||||
}
|
||||
|
||||
$container = new ContainerBuilder();
|
||||
$container->register(Foo::class);
|
||||
|
||||
$container
|
||||
->register('wither', AutowireWither::class)
|
||||
->setAutowired(true);
|
||||
|
||||
(new ResolveClassPass())->process($container);
|
||||
(new AutowireRequiredMethodsPass())->process($container);
|
||||
|
||||
$this->assertSame([['withFoo', [], true]], $container->getDefinition('wither')->getMethodCalls());
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\DependencyInjection\Compiler\AutowireRequiredPropertiesPass;
|
||||
use Symfony\Component\DependencyInjection\Compiler\ResolveClassPass;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Contracts\Service\Attribute\Required;
|
||||
|
||||
require_once __DIR__.'/../Fixtures/includes/autowiring_classes.php';
|
||||
|
||||
@ -43,4 +44,28 @@ class AutowireRequiredPropertiesPassTest extends TestCase
|
||||
$this->assertArrayHasKey('plop', $properties);
|
||||
$this->assertEquals(Bar::class, (string) $properties['plop']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @requires PHP 8
|
||||
*/
|
||||
public function testAttribute()
|
||||
{
|
||||
if (!class_exists(Required::class)) {
|
||||
$this->markTestSkipped('symfony/service-contracts 2.2 required');
|
||||
}
|
||||
|
||||
$container = new ContainerBuilder();
|
||||
$container->register(Foo::class);
|
||||
|
||||
$container->register('property_injection', AutowireProperty::class)
|
||||
->setAutowired(true);
|
||||
|
||||
(new ResolveClassPass())->process($container);
|
||||
(new AutowireRequiredPropertiesPass())->process($container);
|
||||
|
||||
$properties = $container->getDefinition('property_injection')->getProperties();
|
||||
|
||||
$this->assertArrayHasKey('foo', $properties);
|
||||
$this->assertEquals(Foo::class, (string) $properties['foo']);
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ use Psr\Log\LoggerInterface;
|
||||
|
||||
if (PHP_VERSION_ID >= 80000) {
|
||||
require __DIR__.'/uniontype_classes.php';
|
||||
require __DIR__.'/autowiring_classes_80.php';
|
||||
}
|
||||
|
||||
class Foo
|
||||
|
@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace Symfony\Component\DependencyInjection\Tests\Compiler;
|
||||
|
||||
use Symfony\Contracts\Service\Attribute\Required;
|
||||
|
||||
class AutowireSetter
|
||||
{
|
||||
#[Required]
|
||||
public function setFoo(Foo $foo): void
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
class AutowireWither
|
||||
{
|
||||
#[Required]
|
||||
public function withFoo(Foo $foo): static
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
class AutowireProperty
|
||||
{
|
||||
#[Required]
|
||||
public Foo $foo;
|
||||
}
|
@ -28,7 +28,7 @@
|
||||
"minimum-stability": "dev",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "2.1-dev"
|
||||
"dev-master": "2.2-dev"
|
||||
},
|
||||
"thanks": {
|
||||
"name": "symfony/contracts",
|
||||
|
@ -25,7 +25,7 @@
|
||||
"minimum-stability": "dev",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "2.1-dev"
|
||||
"dev-master": "2.2-dev"
|
||||
},
|
||||
"thanks": {
|
||||
"name": "symfony/contracts",
|
||||
|
@ -28,7 +28,7 @@
|
||||
"minimum-stability": "dev",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "2.1-dev"
|
||||
"dev-master": "2.2-dev"
|
||||
},
|
||||
"thanks": {
|
||||
"name": "symfony/contracts",
|
||||
|
@ -27,7 +27,7 @@
|
||||
"minimum-stability": "dev",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "2.1-dev"
|
||||
"dev-master": "2.2-dev"
|
||||
},
|
||||
"thanks": {
|
||||
"name": "symfony/contracts",
|
||||
|
27
src/Symfony/Contracts/Service/Attribute/Required.php
Normal file
27
src/Symfony/Contracts/Service/Attribute/Required.php
Normal file
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Contracts\Service\Attribute;
|
||||
|
||||
use Attribute;
|
||||
|
||||
/**
|
||||
* A required dependency.
|
||||
*
|
||||
* This attribute indicates that a property holds a required dependency. The annotated property or method should be
|
||||
* considered during the instantiation process of the containing class.
|
||||
*
|
||||
* @author Alexander M. Turek <me@derrabus.de>
|
||||
*/
|
||||
#[Attribute(Attribute::TARGET_METHOD | Attribute::TARGET_PROPERTY)]
|
||||
final class Required
|
||||
{
|
||||
}
|
@ -28,7 +28,7 @@
|
||||
"minimum-stability": "dev",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "2.1-dev"
|
||||
"dev-master": "2.2-dev"
|
||||
},
|
||||
"thanks": {
|
||||
"name": "symfony/contracts",
|
||||
|
@ -27,7 +27,7 @@
|
||||
"minimum-stability": "dev",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "2.1-dev"
|
||||
"dev-master": "2.2-dev"
|
||||
},
|
||||
"thanks": {
|
||||
"name": "symfony/contracts",
|
||||
|
@ -49,7 +49,7 @@
|
||||
"minimum-stability": "dev",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "2.1-dev"
|
||||
"dev-master": "2.2-dev"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user