diff --git a/UPGRADE-5.2.md b/UPGRADE-5.2.md
index b563d441e8..3e44ed362a 100644
--- a/UPGRADE-5.2.md
+++ b/UPGRADE-5.2.md
@@ -17,6 +17,17 @@ Mime
* Deprecated `Address::fromString()`, use `Address::create()` instead
+PropertyAccess
+--------------
+
+ * Deprecated passing a boolean as the first argument of `PropertyAccessor::__construct()`.
+ Pass a combination of bitwise flags instead.
+
+PropertyInfo
+------------
+
+ * Dropped the `enable_magic_call_extraction` context option in `ReflectionExtractor::getWriteInfo()` and `ReflectionExtractor::getReadInfo()` in favor of `enable_magic_methods_extraction`.
+
TwigBundle
----------
diff --git a/UPGRADE-6.0.md b/UPGRADE-6.0.md
index 195d644fd5..9f1f9a78f0 100644
--- a/UPGRADE-6.0.md
+++ b/UPGRADE-6.0.md
@@ -108,6 +108,17 @@ PhpUnitBridge
* Removed support for `@expectedDeprecation` annotations, use the `ExpectDeprecationTrait::expectDeprecation()` method instead.
+PropertyAccess
+--------------
+
+ * Dropped support of a boolean as the first argument of `PropertyAccessor::__construct()`.
+ Pass a combination of bitwise flags instead.
+
+PropertyInfo
+------------
+
+ * Dropped the `enable_magic_call_extraction` context option in `ReflectionExtractor::getWriteInfo()` and `ReflectionExtractor::getReadInfo()` in favor of `enable_magic_methods_extraction`.
+
Routing
-------
diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php
index 63a53861cc..4e47cbf75d 100644
--- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php
+++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php
@@ -949,6 +949,8 @@ class Configuration implements ConfigurationInterface
->info('Property access configuration')
->children()
->booleanNode('magic_call')->defaultFalse()->end()
+ ->booleanNode('magic_get')->defaultTrue()->end()
+ ->booleanNode('magic_set')->defaultTrue()->end()
->booleanNode('throw_exception_on_invalid_index')->defaultFalse()->end()
->booleanNode('throw_exception_on_invalid_property_path')->defaultTrue()->end()
->end()
diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
index 316a8ccd33..f922448df7 100644
--- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
+++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
@@ -1429,9 +1429,14 @@ class FrameworkExtension extends Extension
$loader->load('property_access.php');
+ $magicMethods = PropertyAccessor::DISALLOW_MAGIC_METHODS;
+ $magicMethods |= $config['magic_call'] ? PropertyAccessor::MAGIC_CALL : 0;
+ $magicMethods |= $config['magic_get'] ? PropertyAccessor::MAGIC_GET : 0;
+ $magicMethods |= $config['magic_set'] ? PropertyAccessor::MAGIC_SET : 0;
+
$container
->getDefinition('property_accessor')
- ->replaceArgument(0, $config['magic_call'])
+ ->replaceArgument(0, $magicMethods)
->replaceArgument(1, $config['throw_exception_on_invalid_index'])
->replaceArgument(3, $config['throw_exception_on_invalid_property_path'])
->replaceArgument(4, new Reference(PropertyReadInfoExtractorInterface::class, ContainerInterface::NULL_ON_INVALID_REFERENCE))
diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/property_access.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/property_access.php
index da2933d77e..00d8f66b5a 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/property_access.php
+++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/property_access.php
@@ -18,7 +18,7 @@ return static function (ContainerConfigurator $container) {
$container->services()
->set('property_accessor', PropertyAccessor::class)
->args([
- abstract_arg('magicCall, set by the extension'),
+ abstract_arg('magic methods allowed, set by the extension'),
abstract_arg('throwExceptionOnInvalidIndex, set by the extension'),
service('cache.property_access')->ignoreOnInvalid(),
abstract_arg('throwExceptionOnInvalidPropertyPath, set by the extension'),
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 ab0da8c848..9a03ede460 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
@@ -236,6 +236,8 @@
+
+
diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php
index 127baddddb..e47f19aea8 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php
+++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php
@@ -415,6 +415,8 @@ class ConfigurationTest extends TestCase
],
'property_access' => [
'magic_call' => false,
+ 'magic_get' => true,
+ 'magic_set' => true,
'throw_exception_on_invalid_index' => false,
'throw_exception_on_invalid_property_path' => true,
],
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 8f431f8735..dc6954fe89 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
@@ -3,6 +3,8 @@
$container->loadFromExtension('framework', [
'property_access' => [
'magic_call' => true,
+ 'magic_get' => true,
+ 'magic_set' => false,
'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 07e33ae3e8..9406919e92 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 ea527c9821..931b50383f 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
@@ -1,5 +1,7 @@
framework:
property_access:
magic_call: true
+ magic_get: true
+ magic_set: false
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 3bfd523f2e..878b2592f6 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php
+++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php
@@ -86,7 +86,7 @@ abstract class FrameworkExtensionTest extends TestCase
$container = $this->createContainerFromFile('full');
$def = $container->getDefinition('property_accessor');
- $this->assertFalse($def->getArgument(0));
+ $this->assertSame(PropertyAccessor::MAGIC_SET | PropertyAccessor::MAGIC_GET, $def->getArgument(0));
$this->assertFalse($def->getArgument(1));
$this->assertTrue($def->getArgument(3));
}
@@ -95,7 +95,7 @@ abstract class FrameworkExtensionTest extends TestCase
{
$container = $this->createContainerFromFile('property_accessor');
$def = $container->getDefinition('property_accessor');
- $this->assertTrue($def->getArgument(0));
+ $this->assertSame(PropertyAccessor::MAGIC_GET | PropertyAccessor::MAGIC_CALL, $def->getArgument(0));
$this->assertTrue($def->getArgument(1));
$this->assertFalse($def->getArgument(3));
}
diff --git a/src/Symfony/Bundle/FrameworkBundle/composer.json b/src/Symfony/Bundle/FrameworkBundle/composer.json
index fd1659b10f..46b3c5db65 100644
--- a/src/Symfony/Bundle/FrameworkBundle/composer.json
+++ b/src/Symfony/Bundle/FrameworkBundle/composer.json
@@ -83,6 +83,7 @@
"symfony/messenger": "<4.4",
"symfony/mime": "<4.4",
"symfony/property-info": "<4.4",
+ "symfony/property-access": "<5.2",
"symfony/serializer": "<5.2",
"symfony/stopwatch": "<4.4",
"symfony/translation": "<5.0",
diff --git a/src/Symfony/Component/PropertyAccess/CHANGELOG.md b/src/Symfony/Component/PropertyAccess/CHANGELOG.md
index f6a167f859..e46378210d 100644
--- a/src/Symfony/Component/PropertyAccess/CHANGELOG.md
+++ b/src/Symfony/Component/PropertyAccess/CHANGELOG.md
@@ -1,6 +1,12 @@
CHANGELOG
=========
+5.2.0
+-----
+
+ * deprecated passing a boolean as the first argument of `PropertyAccessor::__construct()`, expecting a combination of bitwise flags instead
+ * added the ability to disable usage of the magic `__get` & `__set` methods
+
5.1.0
-----
diff --git a/src/Symfony/Component/PropertyAccess/PropertyAccessor.php b/src/Symfony/Component/PropertyAccess/PropertyAccessor.php
index af7331d52b..18004da4e6 100644
--- a/src/Symfony/Component/PropertyAccess/PropertyAccessor.php
+++ b/src/Symfony/Component/PropertyAccess/PropertyAccessor.php
@@ -38,6 +38,15 @@ use Symfony\Component\PropertyInfo\PropertyWriteInfoExtractorInterface;
*/
class PropertyAccessor implements PropertyAccessorInterface
{
+ /** @var int Allow none of the magic methods */
+ public const DISALLOW_MAGIC_METHODS = ReflectionExtractor::DISALLOW_MAGIC_METHODS;
+ /** @var int Allow magic __get methods */
+ public const MAGIC_GET = ReflectionExtractor::ALLOW_MAGIC_GET;
+ /** @var int Allow magic __set methods */
+ public const MAGIC_SET = ReflectionExtractor::ALLOW_MAGIC_SET;
+ /** @var int Allow magic __call methods */
+ public const MAGIC_CALL = ReflectionExtractor::ALLOW_MAGIC_CALL;
+
private const VALUE = 0;
private const REF = 1;
private const IS_REF_CHAINED = 2;
@@ -45,10 +54,7 @@ class PropertyAccessor implements PropertyAccessorInterface
private const CACHE_PREFIX_WRITE = 'w';
private const CACHE_PREFIX_PROPERTY_PATH = 'p';
- /**
- * @var bool
- */
- private $magicCall;
+ private $magicMethodsFlags;
private $ignoreInvalidIndices;
private $ignoreInvalidProperty;
@@ -68,7 +74,6 @@ class PropertyAccessor implements PropertyAccessorInterface
* @var PropertyWriteInfoExtractorInterface
*/
private $writeInfoExtractor;
-
private $readPropertyCache = [];
private $writePropertyCache = [];
private static $resultProto = [self::VALUE => null];
@@ -76,10 +81,20 @@ class PropertyAccessor implements PropertyAccessorInterface
/**
* Should not be used by application code. Use
* {@link PropertyAccess::createPropertyAccessor()} instead.
+ *
+ * @param int $magicMethods A bitwise combination of the MAGIC_* constants
+ * to specify the allowed magic methods (__get, __set, __call)
+ * or self::DISALLOW_MAGIC_METHODS for none
*/
- public function __construct(bool $magicCall = false, bool $throwExceptionOnInvalidIndex = false, CacheItemPoolInterface $cacheItemPool = null, bool $throwExceptionOnInvalidPropertyPath = true, PropertyReadInfoExtractorInterface $readInfoExtractor = null, PropertyWriteInfoExtractorInterface $writeInfoExtractor = null)
+ public function __construct(/*int */$magicMethods = self::MAGIC_GET | self::MAGIC_SET, bool $throwExceptionOnInvalidIndex = false, CacheItemPoolInterface $cacheItemPool = null, bool $throwExceptionOnInvalidPropertyPath = true, PropertyReadInfoExtractorInterface $readInfoExtractor = null, PropertyWriteInfoExtractorInterface $writeInfoExtractor = null)
{
- $this->magicCall = $magicCall;
+ if (\is_bool($magicMethods)) {
+ trigger_deprecation('symfony/property-info', '5.2', 'Passing a boolean to "%s()" first argument is deprecated since 5.1 and expect a combination of bitwise flags instead (i.e an integer).', __METHOD__);
+
+ $magicMethods = ($magicMethods ? self::MAGIC_CALL : 0) | self::MAGIC_GET | self::MAGIC_SET;
+ }
+
+ $this->magicMethodsFlags = $magicMethods;
$this->ignoreInvalidIndices = !$throwExceptionOnInvalidIndex;
$this->cacheItemPool = $cacheItemPool instanceof NullAdapter ? null : $cacheItemPool; // Replace the NullAdapter by the null value
$this->ignoreInvalidProperty = !$throwExceptionOnInvalidPropertyPath;
@@ -472,7 +487,7 @@ class PropertyAccessor implements PropertyAccessorInterface
$accessor = $this->readInfoExtractor->getReadInfo($class, $property, [
'enable_getter_setter_extraction' => true,
- 'enable_magic_call_extraction' => $this->magicCall,
+ 'enable_magic_methods_extraction' => $this->magicMethodsFlags,
'enable_constructor_extraction' => false,
]);
@@ -592,7 +607,7 @@ class PropertyAccessor implements PropertyAccessorInterface
$mutator = $this->writeInfoExtractor->getWriteInfo($class, $property, [
'enable_getter_setter_extraction' => true,
- 'enable_magic_call_extraction' => $this->magicCall,
+ 'enable_magic_methods_extraction' => $this->magicMethodsFlags,
'enable_constructor_extraction' => false,
'enable_adder_remover_extraction' => $useAdderAndRemover,
]);
diff --git a/src/Symfony/Component/PropertyAccess/PropertyAccessorBuilder.php b/src/Symfony/Component/PropertyAccess/PropertyAccessorBuilder.php
index 41853abbfe..fcda6ce2e5 100644
--- a/src/Symfony/Component/PropertyAccess/PropertyAccessorBuilder.php
+++ b/src/Symfony/Component/PropertyAccess/PropertyAccessorBuilder.php
@@ -22,7 +22,8 @@ use Symfony\Component\PropertyInfo\PropertyWriteInfoExtractorInterface;
*/
class PropertyAccessorBuilder
{
- private $magicCall = false;
+ /** @var int */
+ private $magicMethods = PropertyAccessor::MAGIC_GET | PropertyAccessor::MAGIC_SET;
private $throwExceptionOnInvalidIndex = false;
private $throwExceptionOnInvalidPropertyPath = true;
@@ -41,6 +42,26 @@ class PropertyAccessorBuilder
*/
private $writeInfoExtractor;
+ /**
+ * Enables the use of all magic methods by the PropertyAccessor.
+ */
+ public function enableMagicMethods(): self
+ {
+ $this->magicMethods = PropertyAccessor::MAGIC_GET | PropertyAccessor::MAGIC_SET | PropertyAccessor::MAGIC_CALL;
+
+ return $this;
+ }
+
+ /**
+ * Disable the use of all magic methods by the PropertyAccessor.
+ */
+ public function disableMagicMethods(): self
+ {
+ $this->magicMethods = PropertyAccessor::DISALLOW_MAGIC_METHODS;
+
+ return $this;
+ }
+
/**
* Enables the use of "__call" by the PropertyAccessor.
*
@@ -48,7 +69,27 @@ class PropertyAccessorBuilder
*/
public function enableMagicCall()
{
- $this->magicCall = true;
+ $this->magicMethods |= PropertyAccessor::MAGIC_CALL;
+
+ return $this;
+ }
+
+ /**
+ * Enables the use of "__get" by the PropertyAccessor.
+ */
+ public function enableMagicGet(): self
+ {
+ $this->magicMethods |= PropertyAccessor::MAGIC_GET;
+
+ return $this;
+ }
+
+ /**
+ * Enables the use of "__set" by the PropertyAccessor.
+ */
+ public function enableMagicSet(): self
+ {
+ $this->magicMethods |= PropertyAccessor::MAGIC_SET;
return $this;
}
@@ -60,7 +101,27 @@ class PropertyAccessorBuilder
*/
public function disableMagicCall()
{
- $this->magicCall = false;
+ $this->magicMethods ^= PropertyAccessor::MAGIC_CALL;
+
+ return $this;
+ }
+
+ /**
+ * Disables the use of "__get" by the PropertyAccessor.
+ */
+ public function disableMagicGet(): self
+ {
+ $this->magicMethods ^= PropertyAccessor::MAGIC_GET;
+
+ return $this;
+ }
+
+ /**
+ * Disables the use of "__set" by the PropertyAccessor.
+ */
+ public function disableMagicSet(): self
+ {
+ $this->magicMethods ^= PropertyAccessor::MAGIC_SET;
return $this;
}
@@ -70,7 +131,23 @@ class PropertyAccessorBuilder
*/
public function isMagicCallEnabled()
{
- return $this->magicCall;
+ return (bool) ($this->magicMethods & PropertyAccessor::MAGIC_CALL);
+ }
+
+ /**
+ * @return bool whether the use of "__get" by the PropertyAccessor is enabled
+ */
+ public function isMagicGetEnabled(): bool
+ {
+ return $this->magicMethods & PropertyAccessor::MAGIC_GET;
+ }
+
+ /**
+ * @return bool whether the use of "__set" by the PropertyAccessor is enabled
+ */
+ public function isMagicSetEnabled(): bool
+ {
+ return $this->magicMethods & PropertyAccessor::MAGIC_SET;
}
/**
@@ -206,6 +283,6 @@ class PropertyAccessorBuilder
*/
public function getPropertyAccessor()
{
- return new PropertyAccessor($this->magicCall, $this->throwExceptionOnInvalidIndex, $this->cacheItemPool, $this->throwExceptionOnInvalidPropertyPath, $this->readInfoExtractor, $this->writeInfoExtractor);
+ return new PropertyAccessor($this->magicMethods, $this->throwExceptionOnInvalidIndex, $this->cacheItemPool, $this->throwExceptionOnInvalidPropertyPath, $this->readInfoExtractor, $this->writeInfoExtractor);
}
}
diff --git a/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorBuilderTest.php b/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorBuilderTest.php
index eb46d300da..ce125855dc 100644
--- a/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorBuilderTest.php
+++ b/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorBuilderTest.php
@@ -45,7 +45,21 @@ class PropertyAccessorBuilderTest extends TestCase
$this->assertSame($this->builder, $this->builder->disableMagicCall());
}
- public function testIsMagicCallEnable()
+ public function testTogglingMagicGet()
+ {
+ $this->assertTrue($this->builder->isMagicGetEnabled());
+ $this->assertFalse($this->builder->disableMagicGet()->isMagicCallEnabled());
+ $this->assertTrue($this->builder->enableMagicGet()->isMagicGetEnabled());
+ }
+
+ public function testTogglingMagicSet()
+ {
+ $this->assertTrue($this->builder->isMagicSetEnabled());
+ $this->assertFalse($this->builder->disableMagicSet()->isMagicSetEnabled());
+ $this->assertTrue($this->builder->enableMagicSet()->isMagicSetEnabled());
+ }
+
+ public function testTogglingMagicCall()
{
$this->assertFalse($this->builder->isMagicCallEnabled());
$this->assertTrue($this->builder->enableMagicCall()->isMagicCallEnabled());
diff --git a/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorTest.php b/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorTest.php
index 89e02f334d..c3dac41fdb 100644
--- a/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorTest.php
+++ b/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorTest.php
@@ -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\Exception\NoSuchPropertyException;
use Symfony\Component\PropertyAccess\Exception\UninitializedPropertyException;
use Symfony\Component\PropertyAccess\PropertyAccess;
use Symfony\Component\PropertyAccess\PropertyAccessor;
@@ -130,7 +131,7 @@ class PropertyAccessorTest extends TestCase
public function testGetValueThrowsExceptionIfIndexNotFoundAndIndexExceptionsEnabled($objectOrArray, $path)
{
$this->expectException('Symfony\Component\PropertyAccess\Exception\NoSuchIndexException');
- $this->propertyAccessor = new PropertyAccessor(false, true);
+ $this->propertyAccessor = new PropertyAccessor(PropertyAccessor::DISALLOW_MAGIC_METHODS, true);
$this->propertyAccessor->getValue($objectOrArray, $path);
}
@@ -217,6 +218,15 @@ class PropertyAccessorTest extends TestCase
$this->assertSame('Bernhard', $this->propertyAccessor->getValue(new TestClassMagicGet('Bernhard'), 'magicProperty'));
}
+ public function testGetValueIgnoresMagicGet()
+ {
+ $this->expectException(NoSuchPropertyException::class);
+
+ $propertyAccessor = new PropertyAccessor(PropertyAccessor::DISALLOW_MAGIC_METHODS);
+
+ $propertyAccessor->getValue(new TestClassMagicGet('Bernhard'), 'magicProperty');
+ }
+
public function testGetValueReadsArrayWithMissingIndexForCustomPropertyPath()
{
$object = new \ArrayObject();
@@ -243,7 +253,7 @@ class PropertyAccessorTest extends TestCase
public function testGetValueNotModifyObjectException()
{
- $propertyAccessor = new PropertyAccessor(false, true);
+ $propertyAccessor = new PropertyAccessor(PropertyAccessor::DISALLOW_MAGIC_METHODS, true);
$object = new \stdClass();
$object->firstName = ['Bernhard'];
@@ -261,17 +271,28 @@ class PropertyAccessorTest extends TestCase
$this->propertyAccessor->getValue(new TestClassMagicCall('Bernhard'), 'magicCallProperty');
}
- public function testGetValueReadsMagicCallIfEnabled()
+ /**
+ * @group legacy
+ * @expectedDeprecation Since symfony/property-info 5.2: Passing a boolean to "Symfony\Component\PropertyAccess\PropertyAccessor::__construct()" first argument is deprecated since 5.1 and expect a combination of bitwise flags instead (i.e an integer).
+ */
+ public function testLegacyGetValueReadsMagicCallIfEnabled()
{
$this->propertyAccessor = new PropertyAccessor(true);
$this->assertSame('Bernhard', $this->propertyAccessor->getValue(new TestClassMagicCall('Bernhard'), 'magicCallProperty'));
}
+ public function testGetValueReadsMagicCallIfEnabled()
+ {
+ $this->propertyAccessor = new PropertyAccessor(PropertyAccessor::MAGIC_GET | PropertyAccessor::MAGIC_SET | PropertyAccessor::MAGIC_CALL);
+
+ $this->assertSame('Bernhard', $this->propertyAccessor->getValue(new TestClassMagicCall('Bernhard'), 'magicCallProperty'));
+ }
+
// https://github.com/symfony/symfony/pull/4450
public function testGetValueReadsMagicCallThatReturnsConstant()
{
- $this->propertyAccessor = new PropertyAccessor(true);
+ $this->propertyAccessor = new PropertyAccessor(PropertyAccessor::MAGIC_CALL);
$this->assertSame('constant value', $this->propertyAccessor->getValue(new TestClassMagicCall('Bernhard'), 'constantMagicCallProperty'));
}
@@ -320,7 +341,7 @@ class PropertyAccessorTest extends TestCase
*/
public function testSetValueThrowsNoExceptionIfIndexNotFoundAndIndexExceptionsEnabled($objectOrArray, $path)
{
- $this->propertyAccessor = new PropertyAccessor(false, true);
+ $this->propertyAccessor = new PropertyAccessor(PropertyAccessor::DISALLOW_MAGIC_METHODS, true);
$this->propertyAccessor->setValue($objectOrArray, $path, 'Updated');
$this->assertSame('Updated', $this->propertyAccessor->getValue($objectOrArray, $path));
@@ -343,6 +364,16 @@ class PropertyAccessorTest extends TestCase
$this->assertEquals('Updated', $author->__get('magicProperty'));
}
+ public function testSetValueIgnoresMagicSet()
+ {
+ $this->expectException(NoSuchPropertyException::class);
+ $propertyAccessor = new PropertyAccessor(PropertyAccessor::DISALLOW_MAGIC_METHODS);
+
+ $author = new TestClassMagicGet('Bernhard');
+
+ $propertyAccessor->setValue($author, 'magicProperty', 'Updated');
+ }
+
public function testSetValueThrowsExceptionIfThereAreMissingParameters()
{
$this->expectException('Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException');
@@ -359,7 +390,11 @@ class PropertyAccessorTest extends TestCase
$this->propertyAccessor->setValue($author, 'magicCallProperty', 'Updated');
}
- public function testSetValueUpdatesMagicCallIfEnabled()
+ /**
+ * @group legacy
+ * @expectedDeprecation Since symfony/property-info 5.2: Passing a boolean to "Symfony\Component\PropertyAccess\PropertyAccessor::__construct()" first argument is deprecated since 5.1 and expect a combination of bitwise flags instead (i.e an integer).
+ */
+ public function testLegacySetValueUpdatesMagicCallIfEnabled()
{
$this->propertyAccessor = new PropertyAccessor(true);
@@ -370,6 +405,17 @@ class PropertyAccessorTest extends TestCase
$this->assertEquals('Updated', $author->__call('getMagicCallProperty', []));
}
+ public function testSetValueUpdatesMagicCallIfEnabled()
+ {
+ $this->propertyAccessor = new PropertyAccessor(PropertyAccessor::MAGIC_CALL);
+
+ $author = new TestClassMagicCall('Bernhard');
+
+ $this->propertyAccessor->setValue($author, 'magicCallProperty', 'Updated');
+
+ $this->assertEquals('Updated', $author->__call('getMagicCallProperty', []));
+ }
+
/**
* @dataProvider getPathsWithUnexpectedType
*/
@@ -382,7 +428,7 @@ class PropertyAccessorTest extends TestCase
public function testGetValueWhenArrayValueIsNull()
{
- $this->propertyAccessor = new PropertyAccessor(false, true);
+ $this->propertyAccessor = new PropertyAccessor(PropertyAccessor::DISALLOW_MAGIC_METHODS, true);
$this->assertNull($this->propertyAccessor->getValue(['index' => ['nullable' => null]], '[index][nullable]'));
}
@@ -416,7 +462,7 @@ class PropertyAccessorTest extends TestCase
*/
public function testIsReadableReturnsFalseIfIndexNotFoundAndIndexExceptionsEnabled($objectOrArray, $path)
{
- $this->propertyAccessor = new PropertyAccessor(false, true);
+ $this->propertyAccessor = new PropertyAccessor(PropertyAccessor::DISALLOW_MAGIC_METHODS, true);
// When exceptions are enabled, non-existing indices cannot be read
$this->assertFalse($this->propertyAccessor->isReadable($objectOrArray, $path));
@@ -432,13 +478,24 @@ class PropertyAccessorTest extends TestCase
$this->assertFalse($this->propertyAccessor->isReadable(new TestClassMagicCall('Bernhard'), 'magicCallProperty'));
}
- public function testIsReadableRecognizesMagicCallIfEnabled()
+ /**
+ * @group legacy
+ * @expectedDeprecation Since symfony/property-info 5.2: Passing a boolean to "Symfony\Component\PropertyAccess\PropertyAccessor::__construct()" first argument is deprecated since 5.1 and expect a combination of bitwise flags instead (i.e an integer).
+ */
+ public function testLegacyIsReadableRecognizesMagicCallIfEnabled()
{
$this->propertyAccessor = new PropertyAccessor(true);
$this->assertTrue($this->propertyAccessor->isReadable(new TestClassMagicCall('Bernhard'), 'magicCallProperty'));
}
+ public function testIsReadableRecognizesMagicCallIfEnabled()
+ {
+ $this->propertyAccessor = new PropertyAccessor(PropertyAccessor::MAGIC_CALL);
+
+ $this->assertTrue($this->propertyAccessor->isReadable(new TestClassMagicCall('Bernhard'), 'magicCallProperty'));
+ }
+
/**
* @dataProvider getPathsWithUnexpectedType
*/
@@ -477,7 +534,7 @@ class PropertyAccessorTest extends TestCase
*/
public function testIsWritableReturnsTrueIfIndexNotFoundAndIndexExceptionsEnabled($objectOrArray, $path)
{
- $this->propertyAccessor = new PropertyAccessor(false, true);
+ $this->propertyAccessor = new PropertyAccessor(PropertyAccessor::DISALLOW_MAGIC_METHODS, true);
// Non-existing indices can be written even if exceptions are enabled
$this->assertTrue($this->propertyAccessor->isWritable($objectOrArray, $path));
@@ -493,13 +550,24 @@ class PropertyAccessorTest extends TestCase
$this->assertFalse($this->propertyAccessor->isWritable(new TestClassMagicCall('Bernhard'), 'magicCallProperty'));
}
- public function testIsWritableRecognizesMagicCallIfEnabled()
+ /**
+ * @group legacy
+ * @expectedDeprecation Since symfony/property-info 5.2: Passing a boolean to "Symfony\Component\PropertyAccess\PropertyAccessor::__construct()" first argument is deprecated since 5.1 and expect a combination of bitwise flags instead (i.e an integer).
+ */
+ public function testLegacyIsWritableRecognizesMagicCallIfEnabled()
{
$this->propertyAccessor = new PropertyAccessor(true);
$this->assertTrue($this->propertyAccessor->isWritable(new TestClassMagicCall('Bernhard'), 'magicCallProperty'));
}
+ public function testIsWritableRecognizesMagicCallIfEnabled()
+ {
+ $this->propertyAccessor = new PropertyAccessor(PropertyAccessor::MAGIC_CALL);
+
+ $this->assertTrue($this->propertyAccessor->isWritable(new TestClassMagicCall('Bernhard'), 'magicCallProperty'));
+ }
+
/**
* @dataProvider getPathsWithUnexpectedType
*/
@@ -650,7 +718,7 @@ class PropertyAccessorTest extends TestCase
{
$obj = new TestClass('foo');
- $propertyAccessor = new PropertyAccessor(false, false, new ArrayAdapter());
+ $propertyAccessor = new PropertyAccessor(PropertyAccessor::DISALLOW_MAGIC_METHODS, false, new ArrayAdapter());
$this->assertEquals('foo', $propertyAccessor->getValue($obj, 'publicGetSetter'));
$propertyAccessor->setValue($obj, 'publicGetSetter', 'bar');
$propertyAccessor->setValue($obj, 'publicGetSetter', 'baz');
@@ -664,7 +732,7 @@ class PropertyAccessorTest extends TestCase
$obj->{'a/b'} = '1';
$obj->{'a%2Fb'} = '2';
- $propertyAccessor = new PropertyAccessor(false, false, new ArrayAdapter());
+ $propertyAccessor = new PropertyAccessor(PropertyAccessor::DISALLOW_MAGIC_METHODS, false, new ArrayAdapter());
$this->assertSame('bar', $propertyAccessor->getValue($obj, '@foo'));
$this->assertSame('1', $propertyAccessor->getValue($obj, 'a/b'));
$this->assertSame('2', $propertyAccessor->getValue($obj, 'a%2Fb'));
@@ -685,7 +753,7 @@ class PropertyAccessorTest extends TestCase
$obj = $this->generateAnonymousClass($value);
- $propertyAccessor = new PropertyAccessor(false, false, new ArrayAdapter());
+ $propertyAccessor = new PropertyAccessor(PropertyAccessor::DISALLOW_MAGIC_METHODS, false, new ArrayAdapter());
$this->assertEquals($value, $propertyAccessor->getValue($obj, 'foo'));
}
@@ -713,7 +781,7 @@ class PropertyAccessorTest extends TestCase
$obj = $this->generateAnonymousClass('');
- $propertyAccessor = new PropertyAccessor(false, false, new ArrayAdapter());
+ $propertyAccessor = new PropertyAccessor(PropertyAccessor::DISALLOW_MAGIC_METHODS, false, new ArrayAdapter());
$propertyAccessor->setValue($obj, 'foo', $value);
$this->assertEquals($value, $propertyAccessor->getValue($obj, 'foo'));
diff --git a/src/Symfony/Component/PropertyAccess/composer.json b/src/Symfony/Component/PropertyAccess/composer.json
index f7001b837e..533c6f65b0 100644
--- a/src/Symfony/Component/PropertyAccess/composer.json
+++ b/src/Symfony/Component/PropertyAccess/composer.json
@@ -17,8 +17,9 @@
],
"require": {
"php": ">=7.2.5",
+ "symfony/deprecation-contracts": "^2.1",
"symfony/polyfill-php80": "^1.15",
- "symfony/property-info": "^5.1.1"
+ "symfony/property-info": "^5.2"
},
"require-dev": {
"symfony/cache": "^4.4|^5.0"
diff --git a/src/Symfony/Component/PropertyInfo/CHANGELOG.md b/src/Symfony/Component/PropertyInfo/CHANGELOG.md
index 2925a37a94..75621067ad 100644
--- a/src/Symfony/Component/PropertyInfo/CHANGELOG.md
+++ b/src/Symfony/Component/PropertyInfo/CHANGELOG.md
@@ -1,6 +1,11 @@
CHANGELOG
=========
+5.2.0
+-----
+
+* deprecated the `enable_magic_call_extraction` context option in `ReflectionExtractor::getWriteInfo()` and `ReflectionExtractor::getReadInfo()`. in favor of `enable_magic_methods_extraction`
+
5.1.0
-----
diff --git a/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php b/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php
index 10fa472882..442552724e 100644
--- a/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php
+++ b/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php
@@ -51,6 +51,15 @@ class ReflectionExtractor implements PropertyListExtractorInterface, PropertyTyp
public const ALLOW_PROTECTED = 2;
public const ALLOW_PUBLIC = 4;
+ /** @var int Allow none of the magic methods */
+ public const DISALLOW_MAGIC_METHODS = 0;
+ /** @var int Allow magic __get methods */
+ public const ALLOW_MAGIC_GET = 1 << 0;
+ /** @var int Allow magic __set methods */
+ public const ALLOW_MAGIC_SET = 1 << 1;
+ /** @var int Allow magic __call methods */
+ public const ALLOW_MAGIC_CALL = 1 << 2;
+
private const MAP_TYPES = [
'integer' => Type::BUILTIN_TYPE_INT,
'boolean' => Type::BUILTIN_TYPE_BOOL,
@@ -62,6 +71,7 @@ class ReflectionExtractor implements PropertyListExtractorInterface, PropertyTyp
private $arrayMutatorPrefixes;
private $enableConstructorExtraction;
private $methodReflectionFlags;
+ private $magicMethodsFlags;
private $propertyReflectionFlags;
private $inflector;
@@ -73,7 +83,7 @@ class ReflectionExtractor implements PropertyListExtractorInterface, PropertyTyp
* @param string[]|null $accessorPrefixes
* @param string[]|null $arrayMutatorPrefixes
*/
- public function __construct(array $mutatorPrefixes = null, array $accessorPrefixes = null, array $arrayMutatorPrefixes = null, bool $enableConstructorExtraction = true, int $accessFlags = self::ALLOW_PUBLIC, InflectorInterface $inflector = null)
+ public function __construct(array $mutatorPrefixes = null, array $accessorPrefixes = null, array $arrayMutatorPrefixes = null, bool $enableConstructorExtraction = true, int $accessFlags = self::ALLOW_PUBLIC, InflectorInterface $inflector = null, int $magicMethodsFlags = self::ALLOW_MAGIC_GET | self::ALLOW_MAGIC_SET)
{
$this->mutatorPrefixes = null !== $mutatorPrefixes ? $mutatorPrefixes : self::$defaultMutatorPrefixes;
$this->accessorPrefixes = null !== $accessorPrefixes ? $accessorPrefixes : self::$defaultAccessorPrefixes;
@@ -81,6 +91,7 @@ class ReflectionExtractor implements PropertyListExtractorInterface, PropertyTyp
$this->enableConstructorExtraction = $enableConstructorExtraction;
$this->methodReflectionFlags = $this->getMethodsFlags($accessFlags);
$this->propertyReflectionFlags = $this->getPropertyFlags($accessFlags);
+ $this->magicMethodsFlags = $magicMethodsFlags;
$this->inflector = $inflector ?? new EnglishInflector();
$this->arrayMutatorPrefixesFirst = array_merge($this->arrayMutatorPrefixes, array_diff($this->mutatorPrefixes, $this->arrayMutatorPrefixes));
@@ -230,7 +241,15 @@ class ReflectionExtractor implements PropertyListExtractorInterface, PropertyTyp
}
$allowGetterSetter = $context['enable_getter_setter_extraction'] ?? false;
- $allowMagicCall = $context['enable_magic_call_extraction'] ?? false;
+ $magicMethods = $context['enable_magic_methods_extraction'] ?? $this->magicMethodsFlags;
+ $allowMagicCall = (bool) ($magicMethods & self::ALLOW_MAGIC_CALL);
+ $allowMagicGet = (bool) ($magicMethods & self::ALLOW_MAGIC_GET);
+
+ if (isset($context['enable_magic_call_extraction'])) {
+ trigger_deprecation('symfony/property-info', '5.2', 'Using the "enable_magic_call_extraction" context option in "%s()" is deprecated. Use "enable_magic_methods_extraction" instead.', __METHOD__);
+
+ $allowMagicCall = $context['enable_magic_call_extraction'] ?? false;
+ }
$hasProperty = $reflClass->hasProperty($property);
$camelProp = $this->camelize($property);
@@ -258,7 +277,7 @@ class ReflectionExtractor implements PropertyListExtractorInterface, PropertyTyp
return new PropertyReadInfo(PropertyReadInfo::TYPE_PROPERTY, $property, $this->getReadVisiblityForProperty($reflProperty), $reflProperty->isStatic(), true);
}
- if ($reflClass->hasMethod('__get') && ($reflClass->getMethod('__get')->getModifiers() & $this->methodReflectionFlags)) {
+ if ($allowMagicGet && $reflClass->hasMethod('__get') && ($reflClass->getMethod('__get')->getModifiers() & $this->methodReflectionFlags)) {
return new PropertyReadInfo(PropertyReadInfo::TYPE_PROPERTY, $property, PropertyReadInfo::VISIBILITY_PUBLIC, false, false);
}
@@ -281,7 +300,16 @@ class ReflectionExtractor implements PropertyListExtractorInterface, PropertyTyp
}
$allowGetterSetter = $context['enable_getter_setter_extraction'] ?? false;
- $allowMagicCall = $context['enable_magic_call_extraction'] ?? false;
+ $magicMethods = $context['enable_magic_methods_extraction'] ?? $this->magicMethodsFlags;
+ $allowMagicCall = (bool) ($magicMethods & self::ALLOW_MAGIC_CALL);
+ $allowMagicSet = (bool) ($magicMethods & self::ALLOW_MAGIC_SET);
+
+ if (isset($context['enable_magic_call_extraction'])) {
+ trigger_deprecation('symfony/property-info', '5.2', 'Using the "enable_magic_call_extraction" context option in "%s()" is deprecated. Use "enable_magic_methods_extraction" instead.', __METHOD__);
+
+ $allowMagicCall = $context['enable_magic_call_extraction'] ?? false;
+ }
+
$allowConstruct = $context['enable_constructor_extraction'] ?? $this->enableConstructorExtraction;
$allowAdderRemover = $context['enable_adder_remover_extraction'] ?? true;
@@ -347,12 +375,14 @@ class ReflectionExtractor implements PropertyListExtractorInterface, PropertyTyp
return new PropertyWriteInfo(PropertyWriteInfo::TYPE_PROPERTY, $property, $this->getWriteVisiblityForProperty($reflProperty), $reflProperty->isStatic());
}
- [$accessible, $methodAccessibleErrors] = $this->isMethodAccessible($reflClass, '__set', 2);
- if ($accessible) {
- return new PropertyWriteInfo(PropertyWriteInfo::TYPE_PROPERTY, $property, PropertyWriteInfo::VISIBILITY_PUBLIC, false);
- }
+ if ($allowMagicSet) {
+ [$accessible, $methodAccessibleErrors] = $this->isMethodAccessible($reflClass, '__set', 2);
+ if ($accessible) {
+ return new PropertyWriteInfo(PropertyWriteInfo::TYPE_PROPERTY, $property, PropertyWriteInfo::VISIBILITY_PUBLIC, false);
+ }
- $errors = array_merge($errors, $methodAccessibleErrors);
+ $errors = array_merge($errors, $methodAccessibleErrors);
+ }
if ($allowMagicCall) {
[$accessible, $methodAccessibleErrors] = $this->isMethodAccessible($reflClass, '__call', 2);
diff --git a/src/Symfony/Component/PropertyInfo/Tests/Extractor/ReflectionExtractorTest.php b/src/Symfony/Component/PropertyInfo/Tests/Extractor/ReflectionExtractorTest.php
index 353a41b829..b9bdafbd33 100644
--- a/src/Symfony/Component/PropertyInfo/Tests/Extractor/ReflectionExtractorTest.php
+++ b/src/Symfony/Component/PropertyInfo/Tests/Extractor/ReflectionExtractorTest.php
@@ -12,6 +12,7 @@
namespace Symfony\Component\PropertyInfo\Tests\Extractor;
use PHPUnit\Framework\TestCase;
+use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait;
use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor;
use Symfony\Component\PropertyInfo\PropertyReadInfo;
use Symfony\Component\PropertyInfo\PropertyWriteInfo;
@@ -30,6 +31,8 @@ use Symfony\Component\PropertyInfo\Type;
*/
class ReflectionExtractorTest extends TestCase
{
+ use ExpectDeprecationTrait;
+
/**
* @var ReflectionExtractor
*/
@@ -518,4 +521,30 @@ class ReflectionExtractorTest extends TestCase
[Php71DummyExtended2::class, 'baz', false, true, PropertyWriteInfo::TYPE_ADDER_AND_REMOVER, null, 'addBaz', 'removeBaz', PropertyWriteInfo::VISIBILITY_PUBLIC, false],
];
}
+
+ /**
+ * @group legacy
+ */
+ public function testGetReadInfoDeprecatedEnableMagicCallExtractionInContext()
+ {
+ $this->expectDeprecation('Since symfony/property-info 5.2: Using the "enable_magic_call_extraction" context option in "Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor::getReadInfo()" is deprecated. Use "enable_magic_methods_extraction" instead.');
+
+ $extractor = new ReflectionExtractor();
+ $extractor->getReadInfo(\stdClass::class, 'foo', [
+ 'enable_magic_call_extraction' => true,
+ ]);
+ }
+
+ /**
+ * @group legacy
+ */
+ public function testGetWriteInfoDeprecatedEnableMagicCallExtractionInContext()
+ {
+ $this->expectDeprecation('Since symfony/property-info 5.2: Using the "enable_magic_call_extraction" context option in "Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor::getWriteInfo()" is deprecated. Use "enable_magic_methods_extraction" instead.');
+
+ $extractor = new ReflectionExtractor();
+ $extractor->getWriteInfo(\stdClass::class, 'foo', [
+ 'enable_magic_call_extraction' => true,
+ ]);
+ }
}
diff --git a/src/Symfony/Component/PropertyInfo/composer.json b/src/Symfony/Component/PropertyInfo/composer.json
index c1ce2b757c..9e872695ba 100644
--- a/src/Symfony/Component/PropertyInfo/composer.json
+++ b/src/Symfony/Component/PropertyInfo/composer.json
@@ -24,6 +24,7 @@
],
"require": {
"php": ">=7.2.5",
+ "symfony/deprecation-contracts": "^2.1",
"symfony/polyfill-php80": "^1.15",
"symfony/string": "^5.1"
},