From 5cd10b0fa2042e8b68dcfffa1be9193461a8f20a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CFilip?= Date: Mon, 31 Jan 2022 13:49:45 +0100 Subject: [PATCH] [PropertyAccess] Fix handling of uninitialized property of parent class --- .../PropertyAccess/PropertyAccessor.php | 6 ++--- .../ExtendedUninitializedProperty.php | 17 ++++++++++++++ .../Tests/Fixtures/UninitializedProperty.php | 11 +++++++++ .../Tests/PropertyAccessorTest.php | 23 +++++++++++++++++++ 4 files changed, 54 insertions(+), 3 deletions(-) create mode 100644 src/Symfony/Component/PropertyAccess/Tests/Fixtures/ExtendedUninitializedProperty.php diff --git a/src/Symfony/Component/PropertyAccess/PropertyAccessor.php b/src/Symfony/Component/PropertyAccess/PropertyAccessor.php index d389992ca68fb..aedc7ab068325 100644 --- a/src/Symfony/Component/PropertyAccess/PropertyAccessor.php +++ b/src/Symfony/Component/PropertyAccess/PropertyAccessor.php @@ -401,7 +401,7 @@ private function readProperty(array $zval, string $property, bool $ignoreInvalid && $object instanceof $trace['class'] && preg_match('/Return value (?:of .*::\w+\(\) )?must be of (?:the )?type (\w+), null returned$/', $e->getMessage(), $matches) ) { - throw new AccessException(sprintf('The method "%s::%s()" returned "null", but expected type "%3$s". Did you forget to initialize a property or to make the return type nullable using "?%3$s"?', !str_contains(\get_class($object), "@anonymous\0") ? \get_class($object) : (get_parent_class($object) ?: 'class').'@anonymous', $access[self::ACCESS_NAME], $matches[1]), 0, $e); + throw new AccessException(sprintf('The method "%s::%s()" returned "null", but expected type "%3$s". Did you forget to initialize a property or to make the return type nullable using "?%3$s"?', get_debug_type($object), $access[self::ACCESS_NAME], $matches[1]), 0, $e); } throw $e; @@ -436,8 +436,8 @@ private function readProperty(array $zval, string $property, bool $ignoreInvalid } } catch (\Error $e) { // handle uninitialized properties in PHP >= 7.4 - if (\PHP_VERSION_ID >= 70400 && preg_match('/^Typed property ('.preg_quote(get_debug_type($object), '/').')::\$(\w+) must not be accessed before initialization$/', $e->getMessage(), $matches)) { - $r = new \ReflectionProperty($class, $matches[2]); + if (\PHP_VERSION_ID >= 70400 && preg_match('/^Typed property ([\w\\\\@]+)::\$(\w+) must not be accessed before initialization$/', $e->getMessage(), $matches)) { + $r = new \ReflectionProperty(str_contains($matches[1], '@anonymous') ? $class : $matches[1], $matches[2]); $type = ($type = $r->getType()) instanceof \ReflectionNamedType ? $type->getName() : (string) $type; throw new AccessException(sprintf('The property "%s::$%s" is not readable because it is typed "%s". You should initialize it or declare a default value instead.', $matches[1], $r->getName(), $type), 0, $e); diff --git a/src/Symfony/Component/PropertyAccess/Tests/Fixtures/ExtendedUninitializedProperty.php b/src/Symfony/Component/PropertyAccess/Tests/Fixtures/ExtendedUninitializedProperty.php new file mode 100644 index 0000000000000..e0c5950d258ec --- /dev/null +++ b/src/Symfony/Component/PropertyAccess/Tests/Fixtures/ExtendedUninitializedProperty.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\PropertyAccess\Tests\Fixtures; + +class ExtendedUninitializedProperty extends UninitializedProperty +{ + +} diff --git a/src/Symfony/Component/PropertyAccess/Tests/Fixtures/UninitializedProperty.php b/src/Symfony/Component/PropertyAccess/Tests/Fixtures/UninitializedProperty.php index ac757b4022563..8e057ba9ca165 100644 --- a/src/Symfony/Component/PropertyAccess/Tests/Fixtures/UninitializedProperty.php +++ b/src/Symfony/Component/PropertyAccess/Tests/Fixtures/UninitializedProperty.php @@ -14,4 +14,15 @@ class UninitializedProperty { public string $uninitialized; + private string $privateUninitialized; + + public function getPrivateUninitialized(): string + { + return $this->privateUninitialized; + } + + public function setPrivateUninitialized(string $privateUninitialized): void + { + $this->privateUninitialized = $privateUninitialized; + } } diff --git a/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorTest.php b/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorTest.php index bcecf16ac6168..5e9eca16e758e 100644 --- a/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorTest.php +++ b/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorTest.php @@ -20,6 +20,7 @@ use Symfony\Component\PropertyAccess\Exception\UnexpectedTypeException; use Symfony\Component\PropertyAccess\PropertyAccess; use Symfony\Component\PropertyAccess\PropertyAccessor; +use Symfony\Component\PropertyAccess\Tests\Fixtures\ExtendedUninitializedProperty; use Symfony\Component\PropertyAccess\Tests\Fixtures\ReturnTyped; use Symfony\Component\PropertyAccess\Tests\Fixtures\TestAdderRemoverInvalidArgumentLength; use Symfony\Component\PropertyAccess\Tests\Fixtures\TestAdderRemoverInvalidMethods; @@ -211,6 +212,28 @@ public function testGetValueThrowsExceptionIfUninitializedPropertyOfAnonymousCla $this->propertyAccessor->getValue($object, 'uninitialized'); } + /** + * @requires PHP 7.4 + */ + public function testGetValueThrowsExceptionIfUninitializedNotNullableOfParentClass() + { + $this->expectException(AccessException::class); + $this->expectExceptionMessage('The property "Symfony\Component\PropertyAccess\Tests\Fixtures\UninitializedProperty::$uninitialized" is not readable because it is typed "string". You should initialize it or declare a default value instead.'); + + $this->propertyAccessor->getValue(new ExtendedUninitializedProperty(), 'uninitialized'); + } + + /** + * @requires PHP 7.4 + */ + public function testGetValueThrowsExceptionIfUninitializedNotNullablePropertyWithGetterOfParentClass() + { + $this->expectException(AccessException::class); + $this->expectExceptionMessage('The property "Symfony\Component\PropertyAccess\Tests\Fixtures\UninitializedProperty::$privateUninitialized" is not readable because it is typed "string". You should initialize it or declare a default value instead.'); + + $this->propertyAccessor->getValue(new ExtendedUninitializedProperty(), 'privateUninitialized'); + } + public function testGetValueThrowsExceptionIfUninitializedPropertyWithGetterOfAnonymousStdClass() { $this->expectException(AccessException::class);