Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 4d65001

Browse files
committed
[PropertyAccess] Improved errors when reading uninitialized properties
1 parent a562ba2 commit 4d65001

File tree

4 files changed

+103
-20
lines changed

4 files changed

+103
-20
lines changed

src/Symfony/Component/PropertyAccess/PropertyAccessor.php

Lines changed: 40 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -465,30 +465,50 @@ private function readProperty($zval, $property)
465465
$object = $zval[self::VALUE];
466466
$access = $this->getReadAccessInfo(\get_class($object), $property);
467467

468-
if (self::ACCESS_TYPE_METHOD === $access[self::ACCESS_TYPE]) {
469-
$result[self::VALUE] = $object->{$access[self::ACCESS_NAME]}();
470-
} elseif (self::ACCESS_TYPE_PROPERTY === $access[self::ACCESS_TYPE]) {
471-
$result[self::VALUE] = $object->{$access[self::ACCESS_NAME]};
468+
try {
469+
if (self::ACCESS_TYPE_METHOD === $access[self::ACCESS_TYPE]) {
470+
try {
471+
$result[self::VALUE] = $object->{$access[self::ACCESS_NAME]}();
472+
} catch (\TypeError $e) {
473+
// handle uninitialized properties in PHP >= 7
474+
if (preg_match((sprintf('/Return value of %s::%s\(\) must be of the type (\w+), null returned/', preg_quote(\get_class($object)), $access[self::ACCESS_NAME])), $e->getMessage(), $matches)) {
475+
throw new AccessException(sprintf('The method "%s::%s()" returned "null", but expected type "%3$s". Have you forgotten to initialize a property or to make the return type nullable using "?%3$s" instead?', \get_class($object), $access[self::ACCESS_NAME], $matches[1]), 0, $e);
476+
}
477+
478+
throw $e;
479+
}
480+
} elseif (self::ACCESS_TYPE_PROPERTY === $access[self::ACCESS_TYPE]) {
481+
$result[self::VALUE] = $object->{$access[self::ACCESS_NAME]};
472482

473-
if ($access[self::ACCESS_REF] && isset($zval[self::REF])) {
474-
$result[self::REF] = &$object->{$access[self::ACCESS_NAME]};
483+
if ($access[self::ACCESS_REF] && isset($zval[self::REF])) {
484+
$result[self::REF] = &$object->{$access[self::ACCESS_NAME]};
485+
}
486+
} elseif (!$access[self::ACCESS_HAS_PROPERTY] && property_exists($object, $property)) {
487+
// Needed to support \stdClass instances. We need to explicitly
488+
// exclude $access[self::ACCESS_HAS_PROPERTY], otherwise if
489+
// a *protected* property was found on the class, property_exists()
490+
// returns true, consequently the following line will result in a
491+
// fatal error.
492+
493+
$result[self::VALUE] = $object->$property;
494+
if (isset($zval[self::REF])) {
495+
$result[self::REF] = &$object->$property;
496+
}
497+
} elseif (self::ACCESS_TYPE_MAGIC === $access[self::ACCESS_TYPE]) {
498+
// we call the getter and hope the __call do the job
499+
$result[self::VALUE] = $object->{$access[self::ACCESS_NAME]}();
500+
} else {
501+
throw new NoSuchPropertyException($access[self::ACCESS_NAME]);
475502
}
476-
} elseif (!$access[self::ACCESS_HAS_PROPERTY] && property_exists($object, $property)) {
477-
// Needed to support \stdClass instances. We need to explicitly
478-
// exclude $access[self::ACCESS_HAS_PROPERTY], otherwise if
479-
// a *protected* property was found on the class, property_exists()
480-
// returns true, consequently the following line will result in a
481-
// fatal error.
503+
} catch (\Error $e) {
504+
// handle uninitialized properties in PHP >= 7.4
505+
if (preg_match(sprintf('/Typed property %s::\$(\w+) must not be accessed before initialization/', preg_quote(\get_class($object))), $e->getMessage(), $matches)) {
506+
$r = new \ReflectionProperty(\get_class($object), $matches[1]);
482507

483-
$result[self::VALUE] = $object->$property;
484-
if (isset($zval[self::REF])) {
485-
$result[self::REF] = &$object->$property;
508+
throw new AccessException(sprintf('The property "%s::$%s" is not readable because it is typed "%3$s". You should either initialize it or make it nullable using "?%3$s" instead.', $r->getDeclaringClass()->getName(), $r->getName(), $r->getType()), 0, $e);
486509
}
487-
} elseif (self::ACCESS_TYPE_MAGIC === $access[self::ACCESS_TYPE]) {
488-
// we call the getter and hope the __call do the job
489-
$result[self::VALUE] = $object->{$access[self::ACCESS_NAME]}();
490-
} else {
491-
throw new NoSuchPropertyException($access[self::ACCESS_NAME]);
510+
511+
throw $e;
492512
}
493513

494514
// Objects are always passed around by reference
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\PropertyAccess\Tests\Fixtures;
13+
14+
class UninitializedPrivateProperty
15+
{
16+
private $uninitialized;
17+
18+
public function getUninitialized(): array
19+
{
20+
return $this->uninitialized;
21+
}
22+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\PropertyAccess\Tests\Fixtures;
13+
14+
class UninitializedProperty
15+
{
16+
public string $uninitialized;
17+
}

src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorTest.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
use Symfony\Component\PropertyAccess\Tests\Fixtures\TestSingularAndPluralProps;
2626
use Symfony\Component\PropertyAccess\Tests\Fixtures\Ticket5775Object;
2727
use Symfony\Component\PropertyAccess\Tests\Fixtures\TypeHinted;
28+
use Symfony\Component\PropertyAccess\Tests\Fixtures\UninitializedPrivateProperty;
29+
use Symfony\Component\PropertyAccess\Tests\Fixtures\UninitializedProperty;
2830

2931
class PropertyAccessorTest extends TestCase
3032
{
@@ -118,6 +120,28 @@ public function testGetValueThrowsExceptionIfIndexNotFoundAndIndexExceptionsEnab
118120
$this->propertyAccessor->getValue($objectOrArray, $path);
119121
}
120122

123+
/**
124+
* @requires PHP 7.4
125+
*/
126+
public function testGetValueThrowsExceptionIfUninitializedProperty()
127+
{
128+
$this->expectException('Symfony\Component\PropertyAccess\Exception\AccessException');
129+
$this->expectExceptionMessage('The property "Symfony\Component\PropertyAccess\Tests\Fixtures\UninitializedProperty::$uninitialized" is not readable because it is typed "string". You should either initialize it or make it nullable using "?string" instead.');
130+
131+
$this->propertyAccessor->getValue(new UninitializedProperty(), 'uninitialized');
132+
}
133+
134+
/**
135+
* @requires PHP 7
136+
*/
137+
public function testGetValueThrowsExceptionIfUninitializedPropertyWithGetter()
138+
{
139+
$this->expectException('Symfony\Component\PropertyAccess\Exception\AccessException');
140+
$this->expectExceptionMessage('The method "Symfony\Component\PropertyAccess\Tests\Fixtures\UninitializedPrivateProperty::getUninitialized()" returned "null", but expected type "array". Have you forgotten to initialize a property or to make the return type nullable using "?array" instead?');
141+
142+
$this->propertyAccessor->getValue(new UninitializedPrivateProperty(), 'uninitialized');
143+
}
144+
121145
public function testGetValueThrowsExceptionIfNotArrayAccess()
122146
{
123147
$this->expectException('Symfony\Component\PropertyAccess\Exception\NoSuchIndexException');

0 commit comments

Comments
 (0)