feature #32133 [PropertyAccess] Allow to disable magic __get & __set (ogizanagi)
This PR was merged into the 5.2-dev branch.
Discussion
----------
[PropertyAccess] Allow to disable magic __get & __set
| Q | A
| ------------- | ---
| Branch? | master <!-- see below -->
| Bug fix? | no
| New feature? | yes <!-- please update src/**/CHANGELOG.md files -->
| BC breaks? | no <!-- see https://symfony.com/bc -->
| Deprecations? | yes <!-- please update UPGRADE-*.md and src/**/CHANGELOG.md files -->
| Tests pass? | yes <!-- please add some, will be required by reviewers -->
| Fixed tickets | N/A <!-- #-prefixed issue number(s), if any -->
| License | MIT
| Doc PR | TODO
Working with some legacy code having annoying `__get` & `__set` methods, this would have been useful the same way `__call` can be enabled/disabled.
Commits
-------
11b7bf316e
[PropertyAccess] Allow to disable magic __get & __set
This commit is contained in:
commit
d6980e5f38
|
@ -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
|
||||
----------
|
||||
|
||||
|
|
|
@ -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
|
||||
-------
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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'),
|
||||
|
|
|
@ -236,6 +236,8 @@
|
|||
|
||||
<xsd:complexType name="property_access">
|
||||
<xsd:attribute name="magic-call" type="xsd:boolean" />
|
||||
<xsd:attribute name="magic-get" type="xsd:boolean" />
|
||||
<xsd:attribute name="magic-set" 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>
|
||||
|
|
|
@ -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,
|
||||
],
|
||||
|
|
|
@ -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,
|
||||
],
|
||||
|
|
|
@ -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" throw-exception-on-invalid-property-path="false"/>
|
||||
<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"/>
|
||||
</framework:config>
|
||||
</container>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
-----
|
||||
|
||||
|
|
|
@ -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,
|
||||
]);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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'));
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
-----
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
],
|
||||
"require": {
|
||||
"php": ">=7.2.5",
|
||||
"symfony/deprecation-contracts": "^2.1",
|
||||
"symfony/polyfill-php80": "^1.15",
|
||||
"symfony/string": "^5.1"
|
||||
},
|
||||
|
|
Reference in New Issue