feature #30545 #30536 PropertyAccessor->getValue disable exception (dimabory)

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        | https://github.com/symfony/symfony-docs/pull/11156

Commits
-------

c336696a06 [PropertyAccess] Allow to disable exception on invalid property path when using PropertyAccess::getValue()
This commit is contained in:
Fabien Potencier 2019-04-06 14:57:26 +02:00
commit 4e2b655941
14 changed files with 98 additions and 10 deletions

View File

@ -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
-----

View File

@ -903,6 +903,7 @@ class Configuration implements ConfigurationInterface
->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()

View File

@ -1361,6 +1361,7 @@ class FrameworkExtension extends Extension
->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'])
;
}

View File

@ -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>

View File

@ -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">

View File

@ -249,6 +249,7 @@ class ConfigurationTest extends TestCase
'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),

View File

@ -4,5 +4,6 @@ $container->loadFromExtension('framework', [
'property_access' => [
'magic_call' => true,
'throw_exception_on_invalid_index' => true,
'throw_exception_on_invalid_property_path' => false,
],
]);

View File

@ -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>

View File

@ -2,3 +2,4 @@ framework:
property_access:
magic_call: true
throw_exception_on_invalid_index: true
throw_exception_on_invalid_property_path: false

View File

@ -80,6 +80,7 @@ abstract class FrameworkExtensionTest extends TestCase
$def = $container->getDefinition('property_accessor');
$this->assertFalse($def->getArgument(0));
$this->assertFalse($def->getArgument(1));
$this->assertTrue($def->getArgument(3));
}
public function testPropertyAccessWithOverriddenValues()
@ -88,6 +89,7 @@ abstract class FrameworkExtensionTest extends TestCase
$def = $container->getDefinition('property_accessor');
$this->assertTrue($def->getArgument(0));
$this->assertTrue($def->getArgument(1));
$this->assertFalse($def->getArgument(3));
}
public function testPropertyAccessCache()

View File

@ -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
-----

View File

@ -56,6 +56,7 @@ class PropertyAccessor implements PropertyAccessorInterface
*/
private $magicCall;
private $ignoreInvalidIndices;
private $ignoreInvalidProperty;
/**
* @var CacheItemPoolInterface
@ -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;
}
/**
@ -87,7 +89,7 @@ class PropertyAccessor implements PropertyAccessorInterface
];
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);
@ -313,7 +315,7 @@ class PropertyAccessor implements PropertyAccessorInterface
$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
@ -372,14 +374,15 @@ class PropertyAccessor implements PropertyAccessorInterface
/**
* 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));
@ -411,7 +414,7 @@ class PropertyAccessor implements PropertyAccessorInterface
} 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]);
}

View File

@ -22,6 +22,7 @@ class PropertyAccessorBuilder
{
private $magicCall = false;
private $throwExceptionOnInvalidIndex = false;
private $throwExceptionOnInvalidPropertyPath = true;
/**
* @var CacheItemPoolInterface|null
@ -97,6 +98,43 @@ class PropertyAccessorBuilder
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.
*
@ -128,6 +166,6 @@ class PropertyAccessorBuilder
*/
public function getPropertyAccessor()
{
return new PropertyAccessor($this->magicCall, $this->throwExceptionOnInvalidIndex, $this->cacheItemPool);
return new PropertyAccessor($this->magicCall, $this->throwExceptionOnInvalidIndex, $this->cacheItemPool, $this->throwExceptionOnInvalidPropertyPath);
}
}

View File

@ -14,6 +14,7 @@ namespace Symfony\Component\PropertyAccess\Tests;
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;
@ -100,6 +101,16 @@ class PropertyAccessorTest extends TestCase
$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
*/
@ -618,6 +629,25 @@ class PropertyAccessorTest extends TestCase
$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';