mirror of
https://github.com/danog/Valinor.git
synced 2024-12-02 09:37:46 +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;
|
namespace CuyZ\Valinor\Definition;
|
||||||
|
|
||||||
use CuyZ\Valinor\Definition\Exception\InvalidReflectionParameter;
|
use CuyZ\Valinor\Definition\Exception\InvalidReflectionParameter;
|
||||||
|
use Error;
|
||||||
use ReflectionAttribute;
|
use ReflectionAttribute;
|
||||||
use ReflectionClass;
|
use ReflectionClass;
|
||||||
use ReflectionMethod;
|
use ReflectionMethod;
|
||||||
@ -28,9 +29,23 @@ final class NativeAttributes implements Attributes
|
|||||||
{
|
{
|
||||||
$this->reflectionAttributes = $this->attributes($reflection);
|
$this->reflectionAttributes = $this->attributes($reflection);
|
||||||
|
|
||||||
$attributes = array_map(
|
$attributes = array_filter(
|
||||||
static fn (ReflectionAttribute $attribute) => $attribute->newInstance(),
|
array_map(
|
||||||
$this->reflectionAttributes
|
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);
|
$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\AttributeWithArguments;
|
||||||
use CuyZ\Valinor\Tests\Fixture\Attribute\BasicAttribute;
|
use CuyZ\Valinor\Tests\Fixture\Attribute\BasicAttribute;
|
||||||
|
use CuyZ\Valinor\Tests\Fixture\Attribute\PropertyTargetAttribute;
|
||||||
|
|
||||||
#[BasicAttribute]
|
#[BasicAttribute]
|
||||||
#[AttributeWithArguments('foo', 'bar')]
|
#[AttributeWithArguments('foo', 'bar')]
|
||||||
@ -16,6 +17,10 @@ final class ObjectWithAttributes
|
|||||||
#[AttributeWithArguments('foo', 'bar')]
|
#[AttributeWithArguments('foo', 'bar')]
|
||||||
public bool $property;
|
public bool $property;
|
||||||
|
|
||||||
|
public function __construct(#[PropertyTargetAttribute] public bool $promotedProperty)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
#[BasicAttribute]
|
#[BasicAttribute]
|
||||||
#[AttributeWithArguments('foo', 'bar')]
|
#[AttributeWithArguments('foo', 'bar')]
|
||||||
public function method(#[BasicAttribute] string $parameter): void
|
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\AttributeWithArguments;
|
||||||
use CuyZ\Valinor\Tests\Fixture\Attribute\BasicAttribute;
|
use CuyZ\Valinor\Tests\Fixture\Attribute\BasicAttribute;
|
||||||
use CuyZ\Valinor\Tests\Fixture\Attribute\NestedAttribute;
|
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\ObjectWithAttributes;
|
||||||
use CuyZ\Valinor\Tests\Fixture\Object\ObjectWithNestedAttributes;
|
use CuyZ\Valinor\Tests\Fixture\Object\ObjectWithNestedAttributes;
|
||||||
use Error;
|
use Error;
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
use ReflectionClass;
|
use ReflectionClass;
|
||||||
use ReflectionMethod;
|
use ReflectionMethod;
|
||||||
|
use ReflectionParameter;
|
||||||
use ReflectionProperty;
|
use ReflectionProperty;
|
||||||
use stdClass;
|
use stdClass;
|
||||||
|
|
||||||
@ -322,6 +324,30 @@ final class AttributesCompilerTest extends TestCase
|
|||||||
self::assertTrue($attributes->has(NestedAttribute::class));
|
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
|
private function compile(Attributes $attributes): Attributes
|
||||||
{
|
{
|
||||||
$code = $this->attributesCompiler->compile($attributes);
|
$code = $this->attributesCompiler->compile($attributes);
|
||||||
|
Loading…
Reference in New Issue
Block a user