diff --git a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md
index 981823fd07f10..79aded380fe3c 100644
--- a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md
+++ b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md
@@ -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
-----
diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php
index 076a70c818768..698ef88e021f4 100644
--- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php
+++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php
@@ -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()
diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
index 5ef5ed7b5d269..afca88af956d5 100644
--- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
+++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
@@ -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'])
;
}
diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/property_access.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/property_access.xml
index 4fb4f2ff98abc..424f9f682d796 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/property_access.xml
+++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/property_access.xml
@@ -11,6 +11,7 @@
+
diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd b/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd
index 38e60f6516846..5a807403a01ad 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd
+++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd
@@ -233,6 +233,7 @@
+
diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php
index aa0a2fc921853..a9d7d0a63bf6c 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php
+++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php
@@ -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),
diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/property_accessor.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/property_accessor.php
index b5b060c1baa43..8f431f8735d89 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/property_accessor.php
+++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/property_accessor.php
@@ -4,5 +4,6 @@
'property_access' => [
'magic_call' => true,
'throw_exception_on_invalid_index' => true,
+ 'throw_exception_on_invalid_property_path' => false,
],
]);
diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/property_accessor.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/property_accessor.xml
index 95ddef8288e38..07e33ae3e8d96 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/property_accessor.xml
+++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/property_accessor.xml
@@ -7,6 +7,6 @@
http://symfony.com/schema/dic/symfony https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
-
+
diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/property_accessor.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/property_accessor.yml
index b5fd2718ab112..ea527c9821116 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/property_accessor.yml
+++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/property_accessor.yml
@@ -2,3 +2,4 @@ framework:
property_access:
magic_call: true
throw_exception_on_invalid_index: true
+ throw_exception_on_invalid_property_path: false
diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php
index acc7fbad156e7..1b8a785a303db 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php
+++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php
@@ -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()
@@ -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()
diff --git a/src/Symfony/Component/PropertyAccess/CHANGELOG.md b/src/Symfony/Component/PropertyAccess/CHANGELOG.md
index 970f3545b5702..0a012bb47620d 100644
--- a/src/Symfony/Component/PropertyAccess/CHANGELOG.md
+++ b/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
-----
diff --git a/src/Symfony/Component/PropertyAccess/PropertyAccessor.php b/src/Symfony/Component/PropertyAccess/PropertyAccessor.php
index db9c6b84652ea..891cc5e75ea46 100644
--- a/src/Symfony/Component/PropertyAccess/PropertyAccessor.php
+++ b/src/Symfony/Component/PropertyAccess/PropertyAccessor.php
@@ -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 @@ 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);
@@ -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
@@ -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));
@@ -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]);
}
diff --git a/src/Symfony/Component/PropertyAccess/PropertyAccessorBuilder.php b/src/Symfony/Component/PropertyAccess/PropertyAccessorBuilder.php
index 1db6a1dba23ed..a300bdc6f5c36 100644
--- a/src/Symfony/Component/PropertyAccess/PropertyAccessorBuilder.php
+++ b/src/Symfony/Component/PropertyAccess/PropertyAccessorBuilder.php
@@ -22,6 +22,7 @@ class PropertyAccessorBuilder
{
private $magicCall = false;
private $throwExceptionOnInvalidIndex = false;
+ private $throwExceptionOnInvalidPropertyPath = true;
/**
* @var CacheItemPoolInterface|null
@@ -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.
*
@@ -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);
}
}
diff --git a/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorTest.php b/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorTest.php
index 2a7dd8a83192b..d0cbccf1ec63c 100644
--- a/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorTest.php
+++ b/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorTest.php
@@ -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;
@@ -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
*/
@@ -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';