[PropertyAccess] Allow to disable exception on invalid property path when using PropertyAccess::getValue()
This commit is contained in:
parent
fe7363fff1
commit
c336696a06
@ -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
|
||||
-----
|
||||
|
@ -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()
|
||||
|
@ -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'])
|
||||
;
|
||||
}
|
||||
|
||||
|
@ -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>
|
||||
|
@ -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">
|
||||
|
@ -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),
|
||||
|
@ -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,
|
||||
],
|
||||
]);
|
||||
|
@ -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>
|
||||
|
@ -2,3 +2,4 @@ framework:
|
||||
property_access:
|
||||
magic_call: true
|
||||
throw_exception_on_invalid_index: true
|
||||
throw_exception_on_invalid_property_path: false
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
-----
|
||||
|
||||
|
@ -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
|
||||
@ -374,12 +376,13 @@ class PropertyAccessor implements PropertyAccessorInterface
|
||||
*
|
||||
* @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]);
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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';
|
||||
|
Reference in New Issue
Block a user