Skip to content

Commit 03f159d

Browse files
committed
use bitwise flags to configure when the property accessor should throw
1 parent b34890a commit 03f159d

File tree

6 files changed

+93
-16
lines changed

6 files changed

+93
-16
lines changed

UPGRADE-5.3.md

+6
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,12 @@ PhpunitBridge
3333

3434
* Deprecated the `SetUpTearDownTrait` trait, use original methods with "void" return typehint.
3535

36+
PropertyAccess
37+
--------------
38+
39+
* Deprecated passing a boolean as the second argument of `PropertyAccessor::__construct()`.
40+
Pass a combination of bitwise flags instead.
41+
3642
PropertyInfo
3743
------------
3844

UPGRADE-6.0.md

+2
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,8 @@ PhpUnitBridge
139139
PropertyAccess
140140
--------------
141141

142+
* Dropped support for booleans as the second argument of `PropertyAccessor::__construct()`.
143+
Pass a combination of bitwise flags instead.
142144
* Dropped support of a boolean as the first argument of `PropertyAccessor::__construct()`.
143145
Pass a combination of bitwise flags instead.
144146

src/Symfony/Component/PropertyAccess/CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
CHANGELOG
22
=========
33

4+
5.3.0
5+
-----
6+
7+
* deprecated passing a boolean as the second argument of `PropertyAccessor::__construct()`, expecting a combination of bitwise flags instead
8+
49
5.2.0
510
-----
611

src/Symfony/Component/PropertyAccess/PropertyAccessor.php

+44-6
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@ class PropertyAccessor implements PropertyAccessorInterface
4747
/** @var int Allow magic __call methods */
4848
public const MAGIC_CALL = ReflectionExtractor::ALLOW_MAGIC_CALL;
4949

50+
public const DO_NOT_THROW = 0;
51+
public const THROW_ON_INVALID_INDEX = 1;
52+
public const THROW_ON_INVALID_PROPERTY_PATH = 2;
53+
5054
private const VALUE = 0;
5155
private const REF = 1;
5256
private const IS_REF_CHAINED = 2;
@@ -82,22 +86,56 @@ class PropertyAccessor implements PropertyAccessorInterface
8286
* Should not be used by application code. Use
8387
* {@link PropertyAccess::createPropertyAccessor()} instead.
8488
*
85-
* @param int $magicMethods A bitwise combination of the MAGIC_* constants
86-
* to specify the allowed magic methods (__get, __set, __call)
87-
* or self::DISALLOW_MAGIC_METHODS for none
89+
* @param int $magicMethods A bitwise combination of the MAGIC_* constants
90+
* to specify the allowed magic methods (__get, __set, __call)
91+
* or self::DISALLOW_MAGIC_METHODS for none
92+
* @param int $throw A bitwise combination of the THROW_* constants
93+
* to specify when exceptions should be thrown
94+
* @param PropertyReadInfoExtractorInterface $readInfoExtractor
95+
* @param PropertyWriteInfoExtractorInterface $writeInfoExtractor
8896
*/
89-
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)
97+
public function __construct(/*int */$magicMethods = self::MAGIC_GET | self::MAGIC_SET, /*int */$throw = self::THROW_ON_INVALID_PROPERTY_PATH, CacheItemPoolInterface $cacheItemPool = null, /*PropertyReadInfoExtractorInterface */$readInfoExtractor = null, /*PropertyWriteInfoExtractorInterface */$writeInfoExtractor = null)
9098
{
9199
if (\is_bool($magicMethods)) {
92100
trigger_deprecation('symfony/property-access', '5.2', 'Passing a boolean as the first argument to "%s()" is deprecated. Pass a combination of bitwise flags instead (i.e an integer).', __METHOD__);
93101

94102
$magicMethods = ($magicMethods ? self::MAGIC_CALL : 0) | self::MAGIC_GET | self::MAGIC_SET;
95103
}
96104

105+
if (\is_bool($throw)) {
106+
trigger_deprecation('symfony/property-access', '5.3', 'Passing a boolean as the second argument to "%s()" is deprecated. Pass a combination of bitwise flags instead (i.e an integer).', __METHOD__);
107+
108+
$throw = $throw ? self::THROW_ON_INVALID_INDEX : self::DO_NOT_THROW;
109+
110+
if (!\is_bool($readInfoExtractor)) {
111+
$throw |= self::THROW_ON_INVALID_PROPERTY_PATH;
112+
}
113+
}
114+
115+
if (\is_bool($readInfoExtractor)) {
116+
trigger_deprecation('symfony/property-access', '5.3', 'Passing a boolean as the fourth argument to "%s()" is deprecated. Pass a %s instance instead.', __METHOD__, PropertyReadInfoExtractorInterface::class);
117+
118+
if ($readInfoExtractor) {
119+
$throw |= self::THROW_ON_INVALID_PROPERTY_PATH;
120+
}
121+
122+
$readInfoExtractor = $writeInfoExtractor;
123+
124+
if (null !== $readInfoExtractor && !$readInfoExtractor instanceof PropertyReadInfoExtractorInterface) {
125+
throw new \TypeError(sprintf('Argument 4 passed to "%s()" must be null or an instance of "%s", "%s" given.', __METHOD__, PropertyReadInfoExtractorInterface::class, get_debug_type($readInfoExtractor)));
126+
}
127+
128+
$writeInfoExtractor = 4 < \func_num_args() ? func_get_arg(4) : null;
129+
130+
if (null !== $writeInfoExtractor && !$writeInfoExtractor instanceof PropertyWriteInfoExtractorInterface) {
131+
throw new \TypeError(sprintf('Argument 5 passed to "%s()" must be null or an instance of "%s", "%s" given.', __METHOD__, PropertyWriteInfoExtractorInterface::class, get_debug_type($writeInfoExtractor)));
132+
}
133+
}
134+
97135
$this->magicMethodsFlags = $magicMethods;
98-
$this->ignoreInvalidIndices = !$throwExceptionOnInvalidIndex;
136+
$this->ignoreInvalidIndices = 0 === ($throw & self::THROW_ON_INVALID_INDEX);
99137
$this->cacheItemPool = $cacheItemPool instanceof NullAdapter ? null : $cacheItemPool; // Replace the NullAdapter by the null value
100-
$this->ignoreInvalidProperty = !$throwExceptionOnInvalidPropertyPath;
138+
$this->ignoreInvalidProperty = 0 === ($throw & self::THROW_ON_INVALID_PROPERTY_PATH);
101139
$this->readInfoExtractor = $readInfoExtractor ?? new ReflectionExtractor([], null, null, false);
102140
$this->writeInfoExtractor = $writeInfoExtractor ?? new ReflectionExtractor(['set'], null, null, false);
103141
}

src/Symfony/Component/PropertyAccess/PropertyAccessorBuilder.php

+11-1
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,16 @@ public function getWriteInfoExtractor(): ?PropertyWriteInfoExtractorInterface
283283
*/
284284
public function getPropertyAccessor()
285285
{
286-
return new PropertyAccessor($this->magicMethods, $this->throwExceptionOnInvalidIndex, $this->cacheItemPool, $this->throwExceptionOnInvalidPropertyPath, $this->readInfoExtractor, $this->writeInfoExtractor);
286+
$throw = PropertyAccessor::DO_NOT_THROW;
287+
288+
if ($this->throwExceptionOnInvalidIndex) {
289+
$throw |= PropertyAccessor::THROW_ON_INVALID_INDEX;
290+
}
291+
292+
if ($this->throwExceptionOnInvalidPropertyPath) {
293+
$throw |= PropertyAccessor::THROW_ON_INVALID_PROPERTY_PATH;
294+
}
295+
296+
return new PropertyAccessor($this->magicMethods, $throw, $this->cacheItemPool, $this->readInfoExtractor, $this->writeInfoExtractor);
287297
}
288298
}

src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorTest.php

+25-9
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
namespace Symfony\Component\PropertyAccess\Tests;
1313

1414
use PHPUnit\Framework\TestCase;
15+
use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait;
1516
use Symfony\Component\Cache\Adapter\ArrayAdapter;
1617
use Symfony\Component\PropertyAccess\Exception\NoSuchIndexException;
1718
use Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException;
@@ -35,6 +36,8 @@
3536

3637
class PropertyAccessorTest extends TestCase
3738
{
39+
use ExpectDeprecationTrait;
40+
3841
/**
3942
* @var PropertyAccessor
4043
*/
@@ -131,6 +134,19 @@ public function testGetValueThrowsNoExceptionIfIndexNotFound($objectOrArray, $pa
131134
public function testGetValueThrowsExceptionIfIndexNotFoundAndIndexExceptionsEnabled($objectOrArray, $path)
132135
{
133136
$this->expectException('Symfony\Component\PropertyAccess\Exception\NoSuchIndexException');
137+
$this->propertyAccessor = new PropertyAccessor(PropertyAccessor::DISALLOW_MAGIC_METHODS, PropertyAccessor::THROW_ON_INVALID_INDEX | PropertyAccessor::THROW_ON_INVALID_PROPERTY_PATH);
138+
$this->propertyAccessor->getValue($objectOrArray, $path);
139+
}
140+
141+
/**
142+
* @group legacy
143+
* @dataProvider getPathsWithMissingIndex
144+
*/
145+
public function testGetValueThrowsExceptionIfIndexNotFoundAndIndexExceptionsEnabledUsingBooleanArgument($objectOrArray, $path)
146+
{
147+
$this->expectException('Symfony\Component\PropertyAccess\Exception\NoSuchIndexException');
148+
$this->expectDeprecation('Since symfony/property-access 5.3: Passing a boolean as the second argument to "Symfony\Component\PropertyAccess\PropertyAccessor::__construct()" is deprecated. Pass a combination of bitwise flags instead (i.e an integer).');
149+
134150
$this->propertyAccessor = new PropertyAccessor(PropertyAccessor::DISALLOW_MAGIC_METHODS, true);
135151
$this->propertyAccessor->getValue($objectOrArray, $path);
136152
}
@@ -253,7 +269,7 @@ public function testGetValueNotModifyObject()
253269

254270
public function testGetValueNotModifyObjectException()
255271
{
256-
$propertyAccessor = new PropertyAccessor(PropertyAccessor::DISALLOW_MAGIC_METHODS, true);
272+
$propertyAccessor = new PropertyAccessor(PropertyAccessor::DISALLOW_MAGIC_METHODS, PropertyAccessor::THROW_ON_INVALID_INDEX | PropertyAccessor::THROW_ON_INVALID_PROPERTY_PATH);
257273
$object = new \stdClass();
258274
$object->firstName = ['Bernhard'];
259275

@@ -341,7 +357,7 @@ public function testSetValueThrowsNoExceptionIfIndexNotFound($objectOrArray, $pa
341357
*/
342358
public function testSetValueThrowsNoExceptionIfIndexNotFoundAndIndexExceptionsEnabled($objectOrArray, $path)
343359
{
344-
$this->propertyAccessor = new PropertyAccessor(PropertyAccessor::DISALLOW_MAGIC_METHODS, true);
360+
$this->propertyAccessor = new PropertyAccessor(PropertyAccessor::DISALLOW_MAGIC_METHODS, PropertyAccessor::THROW_ON_INVALID_INDEX | PropertyAccessor::THROW_ON_INVALID_PROPERTY_PATH);
345361
$this->propertyAccessor->setValue($objectOrArray, $path, 'Updated');
346362

347363
$this->assertSame('Updated', $this->propertyAccessor->getValue($objectOrArray, $path));
@@ -428,7 +444,7 @@ public function testSetValueThrowsExceptionIfNotObjectOrArray($objectOrArray, $p
428444

429445
public function testGetValueWhenArrayValueIsNull()
430446
{
431-
$this->propertyAccessor = new PropertyAccessor(PropertyAccessor::DISALLOW_MAGIC_METHODS, true);
447+
$this->propertyAccessor = new PropertyAccessor(PropertyAccessor::DISALLOW_MAGIC_METHODS, PropertyAccessor::THROW_ON_INVALID_INDEX | PropertyAccessor::THROW_ON_INVALID_PROPERTY_PATH);
432448
$this->assertNull($this->propertyAccessor->getValue(['index' => ['nullable' => null]], '[index][nullable]'));
433449
}
434450

@@ -462,7 +478,7 @@ public function testIsReadableReturnsTrueIfIndexNotFound($objectOrArray, $path)
462478
*/
463479
public function testIsReadableReturnsFalseIfIndexNotFoundAndIndexExceptionsEnabled($objectOrArray, $path)
464480
{
465-
$this->propertyAccessor = new PropertyAccessor(PropertyAccessor::DISALLOW_MAGIC_METHODS, true);
481+
$this->propertyAccessor = new PropertyAccessor(PropertyAccessor::DISALLOW_MAGIC_METHODS, PropertyAccessor::THROW_ON_INVALID_INDEX | PropertyAccessor::THROW_ON_INVALID_PROPERTY_PATH);
466482

467483
// When exceptions are enabled, non-existing indices cannot be read
468484
$this->assertFalse($this->propertyAccessor->isReadable($objectOrArray, $path));
@@ -534,7 +550,7 @@ public function testIsWritableReturnsTrueIfIndexNotFound($objectOrArray, $path)
534550
*/
535551
public function testIsWritableReturnsTrueIfIndexNotFoundAndIndexExceptionsEnabled($objectOrArray, $path)
536552
{
537-
$this->propertyAccessor = new PropertyAccessor(PropertyAccessor::DISALLOW_MAGIC_METHODS, true);
553+
$this->propertyAccessor = new PropertyAccessor(PropertyAccessor::DISALLOW_MAGIC_METHODS, PropertyAccessor::THROW_ON_INVALID_INDEX | PropertyAccessor::THROW_ON_INVALID_PROPERTY_PATH);
538554

539555
// Non-existing indices can be written even if exceptions are enabled
540556
$this->assertTrue($this->propertyAccessor->isWritable($objectOrArray, $path));
@@ -718,7 +734,7 @@ public function testCacheReadAccess()
718734
{
719735
$obj = new TestClass('foo');
720736

721-
$propertyAccessor = new PropertyAccessor(PropertyAccessor::DISALLOW_MAGIC_METHODS, false, new ArrayAdapter());
737+
$propertyAccessor = new PropertyAccessor(PropertyAccessor::DISALLOW_MAGIC_METHODS, PropertyAccessor::THROW_ON_INVALID_PROPERTY_PATH, new ArrayAdapter());
722738
$this->assertEquals('foo', $propertyAccessor->getValue($obj, 'publicGetSetter'));
723739
$propertyAccessor->setValue($obj, 'publicGetSetter', 'bar');
724740
$propertyAccessor->setValue($obj, 'publicGetSetter', 'baz');
@@ -732,7 +748,7 @@ public function testAttributeWithSpecialChars()
732748
$obj->{'a/b'} = '1';
733749
$obj->{'a%2Fb'} = '2';
734750

735-
$propertyAccessor = new PropertyAccessor(PropertyAccessor::DISALLOW_MAGIC_METHODS, false, new ArrayAdapter());
751+
$propertyAccessor = new PropertyAccessor(PropertyAccessor::DISALLOW_MAGIC_METHODS, PropertyAccessor::THROW_ON_INVALID_PROPERTY_PATH, new ArrayAdapter());
736752
$this->assertSame('bar', $propertyAccessor->getValue($obj, '@foo'));
737753
$this->assertSame('1', $propertyAccessor->getValue($obj, 'a/b'));
738754
$this->assertSame('2', $propertyAccessor->getValue($obj, 'a%2Fb'));
@@ -753,7 +769,7 @@ public function testAnonymousClassRead()
753769

754770
$obj = $this->generateAnonymousClass($value);
755771

756-
$propertyAccessor = new PropertyAccessor(PropertyAccessor::DISALLOW_MAGIC_METHODS, false, new ArrayAdapter());
772+
$propertyAccessor = new PropertyAccessor(PropertyAccessor::DISALLOW_MAGIC_METHODS, PropertyAccessor::THROW_ON_INVALID_PROPERTY_PATH, new ArrayAdapter());
757773

758774
$this->assertEquals($value, $propertyAccessor->getValue($obj, 'foo'));
759775
}
@@ -781,7 +797,7 @@ public function testAnonymousClassWrite()
781797

782798
$obj = $this->generateAnonymousClass('');
783799

784-
$propertyAccessor = new PropertyAccessor(PropertyAccessor::DISALLOW_MAGIC_METHODS, false, new ArrayAdapter());
800+
$propertyAccessor = new PropertyAccessor(PropertyAccessor::DISALLOW_MAGIC_METHODS, PropertyAccessor::THROW_ON_INVALID_PROPERTY_PATH, new ArrayAdapter());
785801
$propertyAccessor->setValue($obj, 'foo', $value);
786802

787803
$this->assertEquals($value, $propertyAccessor->getValue($obj, 'foo'));

0 commit comments

Comments
 (0)