8000 feature #36117 [PropertyAccess][DX] Added an `UninitializedPropertyEx… · symfony/symfony@dd4d393 · GitHub
[go: up one dir, main page]

Skip to content

Commit dd4d393

Browse files
committed
feature #36117 [PropertyAccess][DX] Added an UninitializedPropertyException (HeahDude)
This PR was merged into the 5.1-dev branch. Discussion ---------- [PropertyAccess][DX] Added an `UninitializedPropertyException` | Q | A | ------------- | --- | Branch? | master | Bug fix? | no | New feature? | yes | Deprecations? | no | Tickets | ~ | License | MIT | Doc PR | TODO Feature version of #36073 for master. Again, better be reviewed without whitespace changes, thanks! Commits ------- 2b2fd12 [PropertyAccess] Added an `UninitializedPropertyException`
2 parents e383b41 + 2b2fd12 commit dd4d393

File tree

4 files changed

+70
-6
lines changed

4 files changed

+70
-6
lines changed

src/Symfony/Component/PropertyAccess/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ CHANGELOG
44
5.1.0
55
-----
66

7+
* Added an `UninitializedPropertyException`
78
* Linking to PropertyInfo extractor to remove a lot of duplicate code
89

910
4.4.0
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
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\Exception;
13+
14+
/**
15+
* Thrown when a property is not initialized.
16+
*
17+
* @author Jules Pietri <jules@heahprod.com>
18+
*/
19+
class UninitializedPropertyException extends AccessException
20+
{
21+
}

src/Symfony/Component/PropertyAccess/PropertyAccessor.php

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
use Symfony\Component\PropertyAccess\Exception\NoSuchIndexException;
2323
use Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException;
2424
use Symfony\Component\PropertyAccess\Exception\UnexpectedTypeException;
25+
use Symfony\Component\PropertyAccess\Exception\UninitializedPropertyException;
2526
use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor;
2627
use Symfony\Component\PropertyInfo\PropertyReadInfo;
2728
use Symfony\Component\PropertyInfo\PropertyReadInfoExtractorInterface;
@@ -389,14 +390,33 @@ private function readProperty(array $zval, string $property, bool $ignoreInvalid
389390
$name = $access->getName();
390391
$type = $access->getType();
391392

392-
if (PropertyReadInfo::TYPE_METHOD === $type) {
393-
$result[self::VALUE] = $object->$name();
394-
} elseif (PropertyReadInfo::TYPE_PROPERTY === $type) {
395-
$result[self::VALUE] = $object->$name;
393+
try {
394+
if (PropertyReadInfo::TYPE_METHOD === $type) {
395+
try {
396+
$result[self::VALUE] = $object->$name();
397+
} catch (\TypeError $e) {
398+
if (preg_match((sprintf('/^Return value of %s::%s\(\) must be of the type (\w+), null returned$/', preg_quote(\get_class($object)), $name)), $e->getMessage(), $matches)) {
399+
throw new UninitializedPropertyException(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), $name, $matches[1]), 0, $e);
400+
}
396401

397-
if (isset($zval[self::REF]) && $access->canBeReference()) {
398-
$result[self::REF] = &$object->$name;
402+
throw $e;
403+
}
404+
} elseif (PropertyReadInfo::TYPE_PROPERTY === $type) {
405+
$result[self::VALUE] = $object->$name;
406+
407+
if (isset($zval[self::REF]) && $access->canBeReference()) {
408+
$result[self::REF] = &$object->$name;
409+
}
399410
}
411+
} catch (\Error $e) {
412+
// handle uninitialized properties in PHP >= 7.4
413+
if (\PHP_VERSION_ID >= 70400 && preg_match('/^Typed property ([\w\\\]+)::\$(\w+) must not be accessed before initialization$/', $e->getMessage(), $matches)) {
414+
$r = new \ReflectionProperty($matches[1], $matches[2]);
415+
416+
throw new UninitializedPropertyException(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()->getName()), 0, $e);
417+
}
418+
419+
throw $e;
400420
}
401421
} elseif ($object instanceof \stdClass && property_exists($object, $property)) {
402422
$result[self::VALUE] = $object->$property;

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

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use PHPUnit\Framework\TestCase;
1515
use Symfony\Component\Cache\Adapter\ArrayAdapter;
1616
use Symfony\Component\PropertyAccess\Exception\NoSuchIndexException;
17+
use Symfony\Component\PropertyAccess\Exception\UninitializedPropertyException;
1718
use Symfony\Component\PropertyAccess\PropertyAccess;
1819
use Symfony\Component\PropertyAccess\PropertyAccessor;
1920
use Symfony\Component\PropertyAccess\Tests\Fixtures\ReturnTyped;
@@ -28,6 +29,8 @@
2829
use Symfony\Component\PropertyAccess\Tests\Fixtures\TestSingularAndPluralProps;
2930
use Symfony\Component\PropertyAccess\Tests\Fixtures\Ticket5775Object;
3031
use Symfony\Component\PropertyAccess\Tests\Fixtures\TypeHinted;
32+
use Symfony\Component\PropertyAccess\Tests\Fixtures\UninitializedPrivateProperty;
33+
use Symfony\Component\PropertyAccess\Tests\Fixtures\UninitializedProperty;
3134

3235
class PropertyAccessorTest extends TestCase
3336
{
@@ -131,6 +134,25 @@ public function testGetValueThrowsExceptionIfIndexNotFoundAndIndexExceptionsEnab
131134
$this->propertyAccessor->getValue($objectOrArray, $path);
132135
}
133136

137+
/**
138+
* @requires PHP 7.4
139+
*/
140+
public function testGetValueThrowsExceptionIfUninitializedProperty()
141+
{
142+
$this->expectException(UninitializedPropertyException::class);
143+
$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.');
144+
145+
$this->propertyAccessor->getValue(new UninitializedProperty(), 'uninitialized');
146+
}
147+
148+
public function testGetValueThrowsExceptionIfUninitializedPropertyWithGetter()
149+
{
150+
$this->expectException(UninitializedPropertyException::class);
151+
$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?');
152+
153+
$this->propertyAccessor->getValue(new UninitializedPrivateProperty(), 'uninitialized');
154+
}
155+
134156
public function testGetValueThrowsExceptionIfNotArrayAccess()
135157
{
136158
$this->expectException('Symfony\Component\PropertyAccess\Exception\NoSuchIndexException');

0 commit comments

Comments
 (0)
0