Skip to content

Commit

Permalink
feature #36117 [PropertyAccess][DX] Added an `UninitializedPropertyEx…
Browse files Browse the repository at this point in the history
…ception` (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`
  • Loading branch information
fabpot committed Mar 18, 2020
2 parents e383b41 + 2b2fd12 commit dd4d393
Show file tree
Hide file tree
Showing 4 changed files with 70 additions and 6 deletions.
1 change: 1 addition & 0 deletions src/Symfony/Component/PropertyAccess/CHANGELOG.md
Expand Up @@ -4,6 +4,7 @@ CHANGELOG
5.1.0
-----

* Added an `UninitializedPropertyException`
* Linking to PropertyInfo extractor to remove a lot of duplicate code

4.4.0
Expand Down
@@ -0,0 +1,21 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\PropertyAccess\Exception;

/**
* Thrown when a property is not initialized.
*
* @author Jules Pietri <jules@heahprod.com>
*/
class UninitializedPropertyException extends AccessException
{
}
32 changes: 26 additions & 6 deletions src/Symfony/Component/PropertyAccess/PropertyAccessor.php
Expand Up @@ -22,6 +22,7 @@
use Symfony\Component\PropertyAccess\Exception\NoSuchIndexException;
use Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException;
use Symfony\Component\PropertyAccess\Exception\UnexpectedTypeException;
use Symfony\Component\PropertyAccess\Exception\UninitializedPropertyException;
use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor;
use Symfony\Component\PropertyInfo\PropertyReadInfo;
use Symfony\Component\PropertyInfo\PropertyReadInfoExtractorInterface;
Expand Down Expand Up @@ -389,14 +390,33 @@ private function readProperty(array $zval, string $property, bool $ignoreInvalid
$name = $access->getName();
$type = $access->getType();

if (PropertyReadInfo::TYPE_METHOD === $type) {
$result[self::VALUE] = $object->$name();
} elseif (PropertyReadInfo::TYPE_PROPERTY === $type) {
$result[self::VALUE] = $object->$name;
try {
if (PropertyReadInfo::TYPE_METHOD === $type) {
try {
$result[self::VALUE] = $object->$name();
} catch (\TypeError $e) {
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)) {
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);
}

if (isset($zval[self::REF]) && $access->canBeReference()) {
$result[self::REF] = &$object->$name;
throw $e;
}
} elseif (PropertyReadInfo::TYPE_PROPERTY === $type) {
$result[self::VALUE] = $object->$name;

if (isset($zval[self::REF]) && $access->canBeReference()) {
$result[self::REF] = &$object->$name;
}
}
} catch (\Error $e) {
// handle uninitialized properties in PHP >= 7.4
if (\PHP_VERSION_ID >= 70400 && preg_match('/^Typed property ([\w\\\]+)::\$(\w+) must not be accessed before initialization$/', $e->getMessage(), $matches)) {
$r = new \ReflectionProperty($matches[1], $matches[2]);

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);
}

throw $e;
}
} elseif ($object instanceof \stdClass && property_exists($object, $property)) {
$result[self::VALUE] = $object->$property;
Expand Down
Expand Up @@ -14,6 +14,7 @@
use PHPUnit\Framework\TestCase;
use Symfony\Component\Cache\Adapter\ArrayAdapter;
use Symfony\Component\PropertyAccess\Exception\NoSuchIndexException;
use Symfony\Component\PropertyAccess\Exception\UninitializedPropertyException;
use Symfony\Component\PropertyAccess\PropertyAccess;
use Symfony\Component\PropertyAccess\PropertyAccessor;
use Symfony\Component\PropertyAccess\Tests\Fixtures\ReturnTyped;
Expand All @@ -28,6 +29,8 @@
use Symfony\Component\PropertyAccess\Tests\Fixtures\TestSingularAndPluralProps;
use Symfony\Component\PropertyAccess\Tests\Fixtures\Ticket5775Object;
use Symfony\Component\PropertyAccess\Tests\Fixtures\TypeHinted;
use Symfony\Component\PropertyAccess\Tests\Fixtures\UninitializedPrivateProperty;
use Symfony\Component\PropertyAccess\Tests\Fixtures\UninitializedProperty;

class PropertyAccessorTest extends TestCase
{
Expand Down Expand Up @@ -131,6 +134,25 @@ public function testGetValueThrowsExceptionIfIndexNotFoundAndIndexExceptionsEnab
$this->propertyAccessor->getValue($objectOrArray, $path);
}

/**
* @requires PHP 7.4
*/
public function testGetValueThrowsExceptionIfUninitializedProperty()
{
$this->expectException(UninitializedPropertyException::class);
$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.');

$this->propertyAccessor->getValue(new UninitializedProperty(), 'uninitialized');
}

public function testGetValueThrowsExceptionIfUninitializedPropertyWithGetter()
{
$this->expectException(UninitializedPropertyException::class);
$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?');

$this->propertyAccessor->getValue(new UninitializedPrivateProperty(), 'uninitialized');
}

public function testGetValueThrowsExceptionIfNotArrayAccess()
{
$this->expectException('Symfony\Component\PropertyAccess\Exception\NoSuchIndexException');
Expand Down

0 comments on commit dd4d393

Please sign in to comment.