Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
feature #30545 #30536 PropertyAccessor->getValue disable exception (d…
…imabory)

This PR was merged into the 4.3-dev branch.

Discussion
----------

#30536 PropertyAccessor->getValue disable exception

| Q             | A
| ------------- | ---
| Branch?       | master
| Bug fix?      | no
| New feature?  | yes
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | #30536
| License       | MIT
| Doc PR        | symfony/symfony-docs#11156

Commits
-------

c336696 [PropertyAccess] Allow to disable exception on invalid property path when using PropertyAccess::getValue()
  • Loading branch information
fabpot committed Apr 6, 2019
2 parents e6eb43b + c336696 commit 4e2b655
Show file tree
Hide file tree
Showing 14 changed files with 98 additions and 10 deletions.
1 change: 1 addition & 0 deletions src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md
Expand Up @@ -30,6 +30,7 @@ CHANGELOG
* Added support for boolean container parameters within routes.
* Added the `messenger:setup-transports` command to setup messenger transports
* Added a `InMemoryTransport` to Messenger. Use it with a DSN starting with `in-memory://`.
* Added `framework.property_access.throw_exception_on_invalid_property_path` config option.

4.2.0
-----
Expand Down
Expand Up @@ -903,6 +903,7 @@ private function addPropertyAccessSection(ArrayNodeDefinition $rootNode)
->children()
->booleanNode('magic_call')->defaultFalse()->end()
->booleanNode('throw_exception_on_invalid_index')->defaultFalse()->end()
->booleanNode('throw_exception_on_invalid_property_path')->defaultTrue()->end()
->end()
->end()
->end()
Expand Down
Expand Up @@ -1361,6 +1361,7 @@ private function registerPropertyAccessConfiguration(array $config, ContainerBui
->getDefinition('property_accessor')
->replaceArgument(0, $config['magic_call'])
->replaceArgument(1, $config['throw_exception_on_invalid_index'])
->replaceArgument(3, $config['throw_exception_on_invalid_property_path'])
;
}

Expand Down
Expand Up @@ -11,6 +11,7 @@
<argument /> <!-- magicCall, set by the extension -->
<argument /> <!-- throwExceptionOnInvalidIndex, set by the extension -->
<argument type="service" id="cache.property_access" on-invalid="ignore" />
<argument /> <!-- throwExceptionOnInvalidPropertyPath, set by the extension -->
</service>
<service id="Symfony\Component\PropertyAccess\PropertyAccessorInterface" alias="property_accessor" />
</services>
Expand Down
Expand Up @@ -233,6 +233,7 @@
<xsd:complexType name="property_access">
<xsd:attribute name="magic-call" type="xsd:boolean" />
<xsd:attribute name="throw-exception-on-invalid-index" type="xsd:boolean" />
<xsd:attribute name="throw-exception-on-invalid-property-path" type="xsd:boolean" />
</xsd:complexType>

<xsd:complexType name="serializer">
Expand Down
Expand Up @@ -249,6 +249,7 @@ protected static function getBundleDefaultConfig()
'property_access' => [
'magic_call' => false,
'throw_exception_on_invalid_index' => false,
'throw_exception_on_invalid_property_path' => true,
],
'property_info' => [
'enabled' => !class_exists(FullStack::class),
Expand Down
Expand Up @@ -4,5 +4,6 @@
'property_access' => [
'magic_call' => true,
'throw_exception_on_invalid_index' => true,
'throw_exception_on_invalid_property_path' => false,
],
]);
Expand Up @@ -7,6 +7,6 @@
http://symfony.com/schema/dic/symfony https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">

<framework:config>
<framework:property-access magic-call="true" throw-exception-on-invalid-index="true" />
<framework:property-access magic-call="true" throw-exception-on-invalid-index="true" throw-exception-on-invalid-property-path="false"/>
</framework:config>
</container>
Expand Up @@ -2,3 +2,4 @@ framework:
property_access:
magic_call: true
throw_exception_on_invalid_index: true
throw_exception_on_invalid_property_path: false
Expand Up @@ -80,6 +80,7 @@ public function testPropertyAccessWithDefaultValue()
$def = $container->getDefinition('property_accessor');
$this->assertFalse($def->getArgument(0));
$this->assertFalse($def->getArgument(1));
$this->assertTrue($def->getArgument(3));
}

public function testPropertyAccessWithOverriddenValues()
Expand All @@ -88,6 +89,7 @@ public function testPropertyAccessWithOverriddenValues()
$def = $container->getDefinition('property_accessor');
$this->assertTrue($def->getArgument(0));
$this->assertTrue($def->getArgument(1));
$this->assertFalse($def->getArgument(3));
}

public function testPropertyAccessCache()
Expand Down
7 changes: 7 additions & 0 deletions src/Symfony/Component/PropertyAccess/CHANGELOG.md
@@ -1,6 +1,13 @@
CHANGELOG
=========

4.3.0
-----

* added a `$throwExceptionOnInvalidPropertyPath` argument to the PropertyAccessor constructor.
* added `enableExceptionOnInvalidPropertyPath()`, `disableExceptionOnInvalidPropertyPath()` and
`isExceptionOnInvalidPropertyPath()` methods to `PropertyAccessorBuilder`

4.0.0
-----

Expand Down
19 changes: 11 additions & 8 deletions src/Symfony/Component/PropertyAccess/PropertyAccessor.php
Expand Up @@ -56,6 +56,7 @@ class PropertyAccessor implements PropertyAccessorInterface
*/
private $magicCall;
private $ignoreInvalidIndices;
private $ignoreInvalidProperty;

/**
* @var CacheItemPoolInterface
Expand All @@ -70,11 +71,12 @@ class PropertyAccessor implements PropertyAccessorInterface
* Should not be used by application code. Use
* {@link PropertyAccess::createPropertyAccessor()} instead.
*/
public function __construct(bool $magicCall = false, bool $throwExceptionOnInvalidIndex = false, CacheItemPoolInterface $cacheItemPool = null)
public function __construct(bool $magicCall = false, bool $throwExceptionOnInvalidIndex = false, CacheItemPoolInterface $cacheItemPool = null, bool $throwExceptionOnInvalidPropertyPath = true)
{
$this->magicCall = $magicCall;
$this->ignoreInvalidIndices = !$throwExceptionOnInvalidIndex;
$this->cacheItemPool = $cacheItemPool instanceof NullAdapter ? null : $cacheItemPool; // Replace the NullAdapter by the null value
$this->ignoreInvalidProperty = !$throwExceptionOnInvalidPropertyPath;
}

/**
Expand All @@ -87,7 +89,7 @@ public function getValue($objectOrArray, $propertyPath)
];

if (\is_object($objectOrArray) && false === strpbrk((string) $propertyPath, '.[')) {
return $this->readProperty($zval, $propertyPath)[self::VALUE];
return $this->readProperty($zval, $propertyPath, $this->ignoreInvalidProperty)[self::VALUE];
}

$propertyPath = $this->getPropertyPath($propertyPath);
Expand Down Expand Up @@ -313,7 +315,7 @@ private function readPropertiesUntil($zval, PropertyPathInterface $propertyPath,

$zval = $this->readIndex($zval, $property);
} else {
$zval = $this->readProperty($zval, $property);
$zval = $this->readProperty($zval, $property, $this->ignoreInvalidProperty);
}

// the final value of the path must not be validated
Expand Down Expand Up @@ -372,14 +374,15 @@ private function readIndex($zval, $index)
/**
* Reads the a property from an object.
*
* @param array $zval The array containing the object to read from
* @param string $property The property to read
* @param array $zval The array containing the object to read from
* @param string $property The property to read
* @param bool $ignoreInvalidProperty Whether to ignore invalid property or throw an exception
*
* @return array The array containing the value of the property
*
* @throws NoSuchPropertyException if the property does not exist or is not public
* @throws NoSuchPropertyException If $ignoreInvalidProperty is false and the property does not exist or is not public
*/
private function readProperty($zval, $property)
private function readProperty($zval, $property, bool $ignoreInvalidProperty = false)
{
if (!\is_object($zval[self::VALUE])) {
throw new NoSuchPropertyException(sprintf('Cannot read property "%s" from an array. Maybe you intended to write the property path as "[%1$s]" instead.', $property));
Expand Down Expand Up @@ -411,7 +414,7 @@ private function readProperty($zval, $property)
} elseif (self::ACCESS_TYPE_MAGIC === $access[self::ACCESS_TYPE]) {
// we call the getter and hope the __call do the job
$result[self::VALUE] = $object->{$access[self::ACCESS_NAME]}();
} else {
} elseif (!$ignoreInvalidProperty) {
throw new NoSuchPropertyException($access[self::ACCESS_NAME]);
}

Expand Down
40 changes: 39 additions & 1 deletion src/Symfony/Component/PropertyAccess/PropertyAccessorBuilder.php
Expand Up @@ -22,6 +22,7 @@ class PropertyAccessorBuilder
{
private $magicCall = false;
private $throwExceptionOnInvalidIndex = false;
private $throwExceptionOnInvalidPropertyPath = true;

/**
* @var CacheItemPoolInterface|null
Expand Down Expand Up @@ -97,6 +98,43 @@ public function isExceptionOnInvalidIndexEnabled()
return $this->throwExceptionOnInvalidIndex;
}

/**
* Enables exceptions when reading a non-existing property.
*
* This has no influence on writing non-existing indices with PropertyAccessorInterface::setValue()
* which are always created on-the-fly.
*
* @return $this
*/
public function enableExceptionOnInvalidPropertyPath()
{
$this->throwExceptionOnInvalidPropertyPath = true;

return $this;
}

/**
* Disables exceptions when reading a non-existing index.
*
* Instead, null is returned when calling PropertyAccessorInterface::getValue() on a non-existing index.
*
* @return $this
*/
public function disableExceptionOnInvalidPropertyPath()
{
$this->throwExceptionOnInvalidPropertyPath = false;

return $this;
}

/**
* @return bool whether an exception is thrown or null is returned when reading a non-existing property
*/
public function isExceptionOnInvalidPropertyPath()
{
return $this->throwExceptionOnInvalidPropertyPath;
}

/**
* Sets a cache system.
*
Expand Down Expand Up @@ -128,6 +166,6 @@ public function getCacheItemPool()
*/
public function getPropertyAccessor()
{
return new PropertyAccessor($this->magicCall, $this->throwExceptionOnInvalidIndex, $this->cacheItemPool);
return new PropertyAccessor($this->magicCall, $this->throwExceptionOnInvalidIndex, $this->cacheItemPool, $this->throwExceptionOnInvalidPropertyPath);
}
}
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\PropertyAccess;
use Symfony\Component\PropertyAccess\PropertyAccessor;
use Symfony\Component\PropertyAccess\Tests\Fixtures\ReturnTyped;
use Symfony\Component\PropertyAccess\Tests\Fixtures\TestClass;
Expand Down Expand Up @@ -100,6 +101,16 @@ public function testGetValueThrowsExceptionIfPropertyNotFound($objectOrArray, $p
$this->propertyAccessor->getValue($objectOrArray, $path);
}

/**
* @dataProvider getPathsWithMissingProperty
*/
public function testGetValueReturnsNullIfPropertyNotFoundAndExceptionIsDisabled($objectOrArray, $path)
{
$this->propertyAccessor = PropertyAccess::createPropertyAccessorBuilder()->disableExceptionOnInvalidPropertyPath()->getPropertyAccessor();

$this->assertNull($this->propertyAccessor->getValue($objectOrArray, $path), $path);
}

/**
* @dataProvider getPathsWithMissingIndex
*/
Expand Down Expand Up @@ -618,6 +629,25 @@ public function testAnonymousClassRead()
$this->assertEquals($value, $propertyAccessor->getValue($obj, 'foo'));
}

/**
* @expectedException \Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException
*/
public function testAnonymousClassReadThrowExceptionOnInvalidPropertyPath()
{
$obj = $this->generateAnonymousClass('bar');

$this->propertyAccessor->getValue($obj, 'invalid_property');
}

public function testAnonymousClassReadReturnsNullOnInvalidPropertyWithDisabledException()
{
$obj = $this->generateAnonymousClass('bar');

$this->propertyAccessor = PropertyAccess::createPropertyAccessorBuilder()->disableExceptionOnInvalidPropertyPath()->getPropertyAccessor();

$this->assertNull($this->propertyAccessor->getValue($obj, 'invalid_property'));
}

public function testAnonymousClassWrite()
{
$value = 'bar';
Expand Down

0 comments on commit 4e2b655

Please sign in to comment.