From b482f1b6fa7aa9621bb79838876f84966b9860a6 Mon Sep 17 00:00:00 2001 From: Yi-Jyun Pan Date: Thu, 21 Nov 2024 23:27:36 +0800 Subject: [PATCH 1/4] Check if the virtual property has a setter in isAllowedProperty A virtual property without setter cannot be written. --- .../Extractor/ReflectionExtractor.php | 11 +++++++++++ .../Extractor/ReflectionExtractorTest.php | 14 ++++++++++++++ .../Tests/Fixtures/VirtualProperties.php | 19 +++++++++++++++++++ 3 files changed, 44 insertions(+) create mode 100644 src/Symfony/Component/PropertyInfo/Tests/Fixtures/VirtualProperties.php diff --git a/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php b/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php index 141233f7afa0e..e0d5acc25d6a3 100644 --- a/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php +++ b/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php @@ -625,6 +625,17 @@ private function isAllowedProperty(string $class, string $property, bool $writeA return false; } + if ( + $writeAccessRequired + && method_exists($reflectionProperty, 'isVirtual') + && method_exists($reflectionProperty, 'hasHook') + && enum_exists('PropertyHookType') + && $reflectionProperty->isVirtual() + && !$reflectionProperty->hasHook(\PropertyHookType::Set) + ) { + return false; + } + return (bool) ($reflectionProperty->getModifiers() & $this->propertyReflectionFlags); } catch (\ReflectionException $e) { // Return false if the property doesn't exist diff --git a/src/Symfony/Component/PropertyInfo/Tests/Extractor/ReflectionExtractorTest.php b/src/Symfony/Component/PropertyInfo/Tests/Extractor/ReflectionExtractorTest.php index e659cfda7784a..7094b7475a81b 100644 --- a/src/Symfony/Component/PropertyInfo/Tests/Extractor/ReflectionExtractorTest.php +++ b/src/Symfony/Component/PropertyInfo/Tests/Extractor/ReflectionExtractorTest.php @@ -28,6 +28,7 @@ use Symfony\Component\PropertyInfo\Tests\Fixtures\Php7Dummy; use Symfony\Component\PropertyInfo\Tests\Fixtures\Php7ParentDummy; use Symfony\Component\PropertyInfo\Tests\Fixtures\Php81Dummy; +use Symfony\Component\PropertyInfo\Tests\Fixtures\VirtualProperties; use Symfony\Component\PropertyInfo\Type; /** @@ -699,4 +700,17 @@ public function testAsymmetricVisibility() $this->assertFalse($this->extractor->isWritable(AsymmetricVisibility::class, 'publicProtected')); $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')); + } } diff --git a/src/Symfony/Component/PropertyInfo/Tests/Fixtures/VirtualProperties.php b/src/Symfony/Component/PropertyInfo/Tests/Fixtures/VirtualProperties.php new file mode 100644 index 0000000000000..38c6d17082ffe --- /dev/null +++ b/src/Symfony/Component/PropertyInfo/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; } +} From 4fdf2961370e78294d22730f44c2ab449f3ad012 Mon Sep 17 00:00:00 2001 From: Yi-Jyun Pan Date: Thu, 21 Nov 2024 23:57:45 +0800 Subject: [PATCH 2/4] Use PHP_VERSION_ID instead of method_exists Psalm does not matter. method_exists may introduce some unneeded overhead. --- .../PropertyInfo/Extractor/ReflectionExtractor.php | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php b/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php index e0d5acc25d6a3..3af92e41cb737 100644 --- a/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php +++ b/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php @@ -625,11 +625,8 @@ private function isAllowedProperty(string $class, string $property, bool $writeA return false; } - if ( - $writeAccessRequired - && method_exists($reflectionProperty, 'isVirtual') - && method_exists($reflectionProperty, 'hasHook') - && enum_exists('PropertyHookType') + if (\PHP_VERSION_ID >= 80400 + && $writeAccessRequired && $reflectionProperty->isVirtual() && !$reflectionProperty->hasHook(\PropertyHookType::Set) ) { From 535b54dd379a547afa2d4f73f8abab9ae2813c8a Mon Sep 17 00:00:00 2001 From: Yi-Jyun Pan Date: Mon, 25 Nov 2024 19:23:39 +0800 Subject: [PATCH 3/4] Add backed properties test --- .../Extractor/ReflectionExtractorTest.php | 25 ++++++++++++------- ...ualProperties.php => HookedProperties.php} | 6 ++++- 2 files changed, 21 insertions(+), 10 deletions(-) rename src/Symfony/Component/PropertyInfo/Tests/Fixtures/{VirtualProperties.php => HookedProperties.php} (62%) diff --git a/src/Symfony/Component/PropertyInfo/Tests/Extractor/ReflectionExtractorTest.php b/src/Symfony/Component/PropertyInfo/Tests/Extractor/ReflectionExtractorTest.php index 7094b7475a81b..3226755916cfe 100644 --- a/src/Symfony/Component/PropertyInfo/Tests/Extractor/ReflectionExtractorTest.php +++ b/src/Symfony/Component/PropertyInfo/Tests/Extractor/ReflectionExtractorTest.php @@ -28,7 +28,7 @@ use Symfony\Component\PropertyInfo\Tests\Fixtures\Php7Dummy; use Symfony\Component\PropertyInfo\Tests\Fixtures\Php7ParentDummy; use Symfony\Component\PropertyInfo\Tests\Fixtures\Php81Dummy; -use Symfony\Component\PropertyInfo\Tests\Fixtures\VirtualProperties; +use Symfony\Component\PropertyInfo\Tests\Fixtures\HookedProperties; use Symfony\Component\PropertyInfo\Type; /** @@ -704,13 +704,20 @@ public function testAsymmetricVisibility() /** * @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')); + public function testHookedProperties() + { + $this->assertTrue($this->extractor->isReadable(HookedProperties::class, 'virtualNoSetHook')); + $this->assertTrue($this->extractor->isReadable(HookedProperties::class, 'virtualSetHookOnly')); + $this->assertTrue($this->extractor->isReadable(HookedProperties::class, 'virtualHook')); + $this->assertFalse($this->extractor->isWritable(HookedProperties::class, 'virtualNoSetHook')); + $this->assertTrue($this->extractor->isWritable(HookedProperties::class, 'virtualSetHookOnly')); + $this->assertTrue($this->extractor->isWritable(HookedProperties::class, 'virtualHook')); + + $this->assertTrue($this->extractor->isReadable(HookedProperties::class, 'backedNoSetHook')); + $this->assertTrue($this->extractor->isReadable(HookedProperties::class, 'backedSetHookOnly')); + $this->assertTrue($this->extractor->isReadable(HookedProperties::class, 'backedHook')); + $this->assertTrue($this->extractor->isWritable(HookedProperties::class, 'backedNoSetHook')); + $this->assertTrue($this->extractor->isWritable(HookedProperties::class, 'backedSetHookOnly')); + $this->assertTrue($this->extractor->isWritable(HookedProperties::class, 'backedHook')); } } diff --git a/src/Symfony/Component/PropertyInfo/Tests/Fixtures/VirtualProperties.php b/src/Symfony/Component/PropertyInfo/Tests/Fixtures/HookedProperties.php similarity index 62% rename from src/Symfony/Component/PropertyInfo/Tests/Fixtures/VirtualProperties.php rename to src/Symfony/Component/PropertyInfo/Tests/Fixtures/HookedProperties.php index 38c6d17082ffe..5fb0ce4bd62c8 100644 --- a/src/Symfony/Component/PropertyInfo/Tests/Fixtures/VirtualProperties.php +++ b/src/Symfony/Component/PropertyInfo/Tests/Fixtures/HookedProperties.php @@ -11,9 +11,13 @@ namespace Symfony\Component\PropertyInfo\Tests\Fixtures; -class VirtualProperties +class HookedProperties { public bool $virtualNoSetHook { get => true; } public bool $virtualSetHookOnly { set => $value; } public bool $virtualHook { get => true; set => $value; } + + public bool $backedNoSetHook = true { get => !$this->backedNoSetHook; } + public bool $backedSetHookOnly = true { set => $this->backedSetHookOnly = $value; } + public bool $backedHook = true { get => !$this->backedHook; set => $this->backedHook = $value; } } From 4b89fc082fad05385e019b5c5e1931975dd5dbef Mon Sep 17 00:00:00 2001 From: Yi-Jyun Pan Date: Mon, 25 Nov 2024 19:53:25 +0800 Subject: [PATCH 4/4] Simplify if-branch --- .../Extractor/ReflectionExtractor.php | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php b/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php index 3af92e41cb737..ca35d5234110a 100644 --- a/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php +++ b/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php @@ -617,20 +617,20 @@ private function isAllowedProperty(string $class, string $property, bool $writeA try { $reflectionProperty = new \ReflectionProperty($class, $property); - if (\PHP_VERSION_ID >= 80100 && $writeAccessRequired && $reflectionProperty->isReadOnly()) { - return false; - } + if ($writeAccessRequired) { + if (\PHP_VERSION_ID >= 80100 && $reflectionProperty->isReadOnly()) { + return false; + } - if (\PHP_VERSION_ID >= 80400 && $writeAccessRequired && ($reflectionProperty->isProtectedSet() || $reflectionProperty->isPrivateSet())) { - return false; - } + if (\PHP_VERSION_ID >= 80400) { + if ($reflectionProperty->isProtectedSet() || $reflectionProperty->isPrivateSet()) { + return false; + } - if (\PHP_VERSION_ID >= 80400 - && $writeAccessRequired - && $reflectionProperty->isVirtual() - && !$reflectionProperty->hasHook(\PropertyHookType::Set) - ) { - return false; + if ($reflectionProperty->isVirtual() && !$reflectionProperty->hasHook(\PropertyHookType::Set)) { + return false; + } + } } return (bool) ($reflectionProperty->getModifiers() & $this->propertyReflectionFlags);