mirror of
https://github.com/danog/Valinor.git
synced 2024-11-30 04:39:05 +01:00
fix: handle native attribute on promoted parameter
Handles race condition when the attribute is affected to a property or parameter that was promoted, in this case the attribute will be applied to both `ParameterReflection` and `PropertyReflection`, but the target argument inside the attribute class is configured to support only one of them (parameter or property). More details: https://wiki.php.net/rfc/constructor_promotion#attributes
This commit is contained in:
parent
17328d86db
commit
897ca9b65e
@ -5,6 +5,7 @@ declare(strict_types=1);
|
||||
namespace CuyZ\Valinor\Definition;
|
||||
|
||||
use CuyZ\Valinor\Definition\Exception\InvalidReflectionParameter;
|
||||
use Error;
|
||||
use ReflectionAttribute;
|
||||
use ReflectionClass;
|
||||
use ReflectionMethod;
|
||||
@ -28,9 +29,23 @@ final class NativeAttributes implements Attributes
|
||||
{
|
||||
$this->reflectionAttributes = $this->attributes($reflection);
|
||||
|
||||
$attributes = array_map(
|
||||
static fn (ReflectionAttribute $attribute) => $attribute->newInstance(),
|
||||
$this->reflectionAttributes
|
||||
$attributes = array_filter(
|
||||
array_map(
|
||||
static function (ReflectionAttribute $attribute) {
|
||||
try {
|
||||
return $attribute->newInstance();
|
||||
} catch (Error) {
|
||||
// Race condition when the attribute is affected to a property/parameter
|
||||
// that was PROMOTED, in this case the attribute will be applied to both
|
||||
// ParameterReflection AND PropertyReflection, BUT the target arg inside the attribute
|
||||
// class is configured to support only ONE of them (parameter OR property)
|
||||
// https://wiki.php.net/rfc/constructor_promotion#attributes for more details.
|
||||
// Ignore attribute if the instantiation failed.
|
||||
return null;
|
||||
}
|
||||
},
|
||||
$this->reflectionAttributes,
|
||||
),
|
||||
);
|
||||
|
||||
$this->delegate = new AttributesContainer(...$attributes);
|
||||
|
10
tests/Fixture/Attribute/PropertyTargetAttribute.php
Normal file
10
tests/Fixture/Attribute/PropertyTargetAttribute.php
Normal file
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace CuyZ\Valinor\Tests\Fixture\Attribute;
|
||||
|
||||
use Attribute;
|
||||
|
||||
#[Attribute(Attribute::TARGET_PROPERTY)]
|
||||
final class PropertyTargetAttribute
|
||||
{
|
||||
}
|
@ -6,6 +6,7 @@ namespace CuyZ\Valinor\Tests\Fixture\Object;
|
||||
|
||||
use CuyZ\Valinor\Tests\Fixture\Attribute\AttributeWithArguments;
|
||||
use CuyZ\Valinor\Tests\Fixture\Attribute\BasicAttribute;
|
||||
use CuyZ\Valinor\Tests\Fixture\Attribute\PropertyTargetAttribute;
|
||||
|
||||
#[BasicAttribute]
|
||||
#[AttributeWithArguments('foo', 'bar')]
|
||||
@ -16,6 +17,10 @@ final class ObjectWithAttributes
|
||||
#[AttributeWithArguments('foo', 'bar')]
|
||||
public bool $property;
|
||||
|
||||
public function __construct(#[PropertyTargetAttribute] public bool $promotedProperty)
|
||||
{
|
||||
}
|
||||
|
||||
#[BasicAttribute]
|
||||
#[AttributeWithArguments('foo', 'bar')]
|
||||
public function method(#[BasicAttribute] string $parameter): void
|
||||
|
@ -14,12 +14,14 @@ use CuyZ\Valinor\Tests\Fixture\Annotation\BasicAnnotation;
|
||||
use CuyZ\Valinor\Tests\Fixture\Attribute\AttributeWithArguments;
|
||||
use CuyZ\Valinor\Tests\Fixture\Attribute\BasicAttribute;
|
||||
use CuyZ\Valinor\Tests\Fixture\Attribute\NestedAttribute;
|
||||
use CuyZ\Valinor\Tests\Fixture\Attribute\PropertyTargetAttribute;
|
||||
use CuyZ\Valinor\Tests\Fixture\Object\ObjectWithAttributes;
|
||||
use CuyZ\Valinor\Tests\Fixture\Object\ObjectWithNestedAttributes;
|
||||
use Error;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use ReflectionClass;
|
||||
use ReflectionMethod;
|
||||
use ReflectionParameter;
|
||||
use ReflectionProperty;
|
||||
use stdClass;
|
||||
|
||||
@ -322,6 +324,30 @@ final class AttributesCompilerTest extends TestCase
|
||||
self::assertTrue($attributes->has(NestedAttribute::class));
|
||||
}
|
||||
|
||||
/**
|
||||
* @requires PHP >= 8
|
||||
*/
|
||||
public function test_compiles_native_php_attributes_for_promoted_property_with_property_target_attribute(): void
|
||||
{
|
||||
$reflection = new ReflectionProperty(ObjectWithAttributes::class, 'promotedProperty');
|
||||
$attributes = $this->compile(new NativeAttributes($reflection));
|
||||
|
||||
self::assertCount(1, $attributes);
|
||||
self::assertTrue($attributes->has(PropertyTargetAttribute::class));
|
||||
}
|
||||
|
||||
/**
|
||||
* @requires PHP >= 8
|
||||
*/
|
||||
public function test_compiles_an_empty_attributes_instance_for_promoted_parameter_with_property_target_attribute(): void
|
||||
{
|
||||
$reflection = new ReflectionParameter([ObjectWithAttributes::class, '__construct'], 'promotedProperty');
|
||||
$attributes = $this->compile(new NativeAttributes($reflection));
|
||||
|
||||
self::assertCount(0, $attributes);
|
||||
self::assertInstanceOf(EmptyAttributes::class, $attributes);
|
||||
}
|
||||
|
||||
private function compile(Attributes $attributes): Attributes
|
||||
{
|
||||
$code = $this->attributesCompiler->compile($attributes);
|
||||
|
Loading…
Reference in New Issue
Block a user