diff --git a/Extractor/ReflectionExtractor.php b/Extractor/ReflectionExtractor.php index f20196d..be1a471 100644 --- a/Extractor/ReflectionExtractor.php +++ b/Extractor/ReflectionExtractor.php @@ -709,12 +709,18 @@ private function isAllowedProperty(string $class, string $property, bool $writeA try { $reflectionProperty = new \ReflectionProperty($class, $property); - if ($writeAccessRequired && $reflectionProperty->isReadOnly()) { - return false; - } + if ($writeAccessRequired) { + if ($reflectionProperty->isReadOnly()) { + return false; + } + + if (\PHP_VERSION_ID >= 80400 && ($reflectionProperty->isProtectedSet() || $reflectionProperty->isPrivateSet())) { + return false; + } - if (\PHP_VERSION_ID >= 80400 && $writeAccessRequired && ($reflectionProperty->isProtectedSet() || $reflectionProperty->isPrivateSet())) { - return false; + if (\PHP_VERSION_ID >= 80400 &&$reflectionProperty->isVirtual() && !$reflectionProperty->hasHook(\PropertyHookType::Set)) { + return false; + } } return (bool) ($reflectionProperty->getModifiers() & $this->propertyReflectionFlags); @@ -955,6 +961,20 @@ private function getReadVisiblityForMethod(\ReflectionMethod $reflectionMethod): private function getWriteVisiblityForProperty(\ReflectionProperty $reflectionProperty): string { + if (\PHP_VERSION_ID >= 80400) { + if ($reflectionProperty->isVirtual() && !$reflectionProperty->hasHook(\PropertyHookType::Set)) { + return PropertyWriteInfo::VISIBILITY_PRIVATE; + } + + if ($reflectionProperty->isPrivateSet()) { + return PropertyWriteInfo::VISIBILITY_PRIVATE; + } + + if ($reflectionProperty->isProtectedSet()) { + return PropertyWriteInfo::VISIBILITY_PROTECTED; + } + } + if ($reflectionProperty->isPrivate()) { return PropertyWriteInfo::VISIBILITY_PRIVATE; } diff --git a/Tests/Extractor/ReflectionExtractorTest.php b/Tests/Extractor/ReflectionExtractorTest.php index 8d4d472..13e9db7 100644 --- a/Tests/Extractor/ReflectionExtractorTest.php +++ b/Tests/Extractor/ReflectionExtractorTest.php @@ -32,6 +32,7 @@ use Symfony\Component\PropertyInfo\Tests\Fixtures\Php81Dummy; use Symfony\Component\PropertyInfo\Tests\Fixtures\Php82Dummy; use Symfony\Component\PropertyInfo\Tests\Fixtures\SnakeCaseDummy; +use Symfony\Component\PropertyInfo\Tests\Fixtures\VirtualProperties; use Symfony\Component\PropertyInfo\Type as LegacyType; use Symfony\Component\TypeInfo\Type; use Symfony\Component\TypeInfo\Type\NullableType; @@ -702,6 +703,69 @@ public function testAsymmetricVisibility() $this->assertFalse($this->extractor->isWritable(AsymmetricVisibility::class, 'protectedPrivate')); } + /** + * @requires PHP 8.4 + */ + public function testVirtualProperties() + { + $this->assertTrue($this->extractor->isReadable(VirtualProperties::class, 'virtualNoSetHook')); + $this->assertTrue($this->extractor->isReadable(VirtualProperties::class, 'virtualSetHookOnly')); + $this->assertTrue($this->extractor->isReadable(VirtualProperties::class, 'virtualHook')); + $this->assertFalse($this->extractor->isWritable(VirtualProperties::class, 'virtualNoSetHook')); + $this->assertTrue($this->extractor->isWritable(VirtualProperties::class, 'virtualSetHookOnly')); + $this->assertTrue($this->extractor->isWritable(VirtualProperties::class, 'virtualHook')); + } + + /** + * @dataProvider provideAsymmetricVisibilityMutator + * @requires PHP 8.4 + */ + public function testAsymmetricVisibilityMutator(string $property, string $readVisibility, string $writeVisibility) + { + $extractor = new ReflectionExtractor(null, null, null, true, ReflectionExtractor::ALLOW_PUBLIC | ReflectionExtractor::ALLOW_PROTECTED | ReflectionExtractor::ALLOW_PRIVATE); + $readMutator = $extractor->getReadInfo(AsymmetricVisibility::class, $property); + $writeMutator = $extractor->getWriteInfo(AsymmetricVisibility::class, $property, [ + 'enable_getter_setter_extraction' => true, + ]); + + $this->assertSame(PropertyReadInfo::TYPE_PROPERTY, $readMutator->getType()); + $this->assertSame(PropertyWriteInfo::TYPE_PROPERTY, $writeMutator->getType()); + $this->assertSame($readVisibility, $readMutator->getVisibility()); + $this->assertSame($writeVisibility, $writeMutator->getVisibility()); + } + + public static function provideAsymmetricVisibilityMutator(): iterable + { + yield ['publicPrivate', PropertyReadInfo::VISIBILITY_PUBLIC, PropertyWriteInfo::VISIBILITY_PRIVATE]; + yield ['publicProtected', PropertyReadInfo::VISIBILITY_PUBLIC, PropertyWriteInfo::VISIBILITY_PROTECTED]; + yield ['protectedPrivate', PropertyReadInfo::VISIBILITY_PROTECTED, PropertyWriteInfo::VISIBILITY_PRIVATE]; + } + + /** + * @dataProvider provideVirtualPropertiesMutator + * @requires PHP 8.4 + */ + public function testVirtualPropertiesMutator(string $property, string $readVisibility, string $writeVisibility) + { + $extractor = new ReflectionExtractor(null, null, null, true, ReflectionExtractor::ALLOW_PUBLIC | ReflectionExtractor::ALLOW_PROTECTED | ReflectionExtractor::ALLOW_PRIVATE); + $readMutator = $extractor->getReadInfo(VirtualProperties::class, $property); + $writeMutator = $extractor->getWriteInfo(VirtualProperties::class, $property, [ + 'enable_getter_setter_extraction' => true, + ]); + + $this->assertSame(PropertyReadInfo::TYPE_PROPERTY, $readMutator->getType()); + $this->assertSame(PropertyWriteInfo::TYPE_PROPERTY, $writeMutator->getType()); + $this->assertSame($readVisibility, $readMutator->getVisibility()); + $this->assertSame($writeVisibility, $writeMutator->getVisibility()); + } + + public static function provideVirtualPropertiesMutator(): iterable + { + yield ['virtualNoSetHook', PropertyReadInfo::VISIBILITY_PUBLIC, PropertyWriteInfo::VISIBILITY_PRIVATE]; + yield ['virtualSetHookOnly', PropertyReadInfo::VISIBILITY_PUBLIC, PropertyWriteInfo::VISIBILITY_PUBLIC]; + yield ['virtualHook', PropertyReadInfo::VISIBILITY_PUBLIC, PropertyWriteInfo::VISIBILITY_PUBLIC]; + } + /** * @dataProvider typesProvider */ diff --git a/Tests/Fixtures/VirtualProperties.php b/Tests/Fixtures/VirtualProperties.php new file mode 100644 index 0000000..38c6d17 --- /dev/null +++ b/Tests/Fixtures/VirtualProperties.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\PropertyInfo\Tests\Fixtures; + +class VirtualProperties +{ + public bool $virtualNoSetHook { get => true; } + public bool $virtualSetHookOnly { set => $value; } + public bool $virtualHook { get => true; set => $value; } +} diff --git a/composer.json b/composer.json index 0de87c0..70a57ef 100644 --- a/composer.json +++ b/composer.json @@ -37,8 +37,7 @@ "conflict": { "phpdocumentor/reflection-docblock": "<5.2", "phpdocumentor/type-resolver": "<1.5.1", - "symfony/dependency-injection": "<6.4", - "symfony/serializer": "<6.4" + "symfony/dependency-injection": "<6.4" }, "autoload": { "psr-4": { "Symfony\\Component\\PropertyInfo\\": "" },