Skip to content

Commit 80e0378

Browse files
committed
[PropertyAccess] Fix compatibility with PHP 8.4 asymmetric visibility
1 parent 8cc779d commit 80e0378

File tree

3 files changed

+89
-2
lines changed

3 files changed

+89
-2
lines changed

PropertyAccessor.php

+9-2
Original file line numberDiff line numberDiff line change
@@ -640,15 +640,22 @@ private function getWriteInfo(string $class, string $property, mixed $value): Pr
640640
*/
641641
private function isPropertyWritable(object $object, string $property): bool
642642
{
643+
if ($object instanceof \stdClass && property_exists($object, $property)) {
644+
return true;
645+
}
646+
643647
$mutatorForArray = $this->getWriteInfo($object::class, $property, []);
648+
if (PropertyWriteInfo::TYPE_PROPERTY === $mutatorForArray->getType()) {
649+
return $mutatorForArray->getVisibility() === 'public';
650+
}
644651

645-
if (PropertyWriteInfo::TYPE_NONE !== $mutatorForArray->getType() || ($object instanceof \stdClass && property_exists($object, $property))) {
652+
if (PropertyWriteInfo::TYPE_NONE !== $mutatorForArray->getType()) {
646653
return true;
647654
}
648655

649656
$mutator = $this->getWriteInfo($object::class, $property, '');
650657

651-
return PropertyWriteInfo::TYPE_NONE !== $mutator->getType() || ($object instanceof \stdClass && property_exists($object, $property));
658+
return PropertyWriteInfo::TYPE_NONE !== $mutator->getType();
652659
}
653660

654661
/**
+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\PropertyAccess\Tests\Fixtures;
13+
14+
class AsymmetricVisibility
15+
{
16+
public public(set) mixed $publicPublic = null;
17+
public protected(set) mixed $publicProtected = null;
18+
public private(set) mixed $publicPrivate = null;
19+
private private(set) mixed $privatePrivate = null;
20+
public bool $virtualNoSetHook { get => true; }
21+
}

Tests/PropertyAccessorTest.php

+59
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
use Symfony\Component\PropertyAccess\PropertyAccess;
2121
use Symfony\Component\PropertyAccess\PropertyAccessor;
2222
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
23+
use Symfony\Component\PropertyAccess\Tests\Fixtures\AsymmetricVisibility;
2324
use Symfony\Component\PropertyAccess\Tests\Fixtures\ExtendedUninitializedProperty;
2425
use Symfony\Component\PropertyAccess\Tests\Fixtures\ReturnTyped;
2526
use Symfony\Component\PropertyAccess\Tests\Fixtures\TestAdderRemoverInvalidArgumentLength;
@@ -1023,4 +1024,62 @@ private function createUninitializedObjectPropertyGhost(): UninitializedObjectPr
10231024
return $class::createLazyGhost(initializer: function ($instance) {
10241025
});
10251026
}
1027+
1028+
/**
1029+
* @requires PHP 8.4
1030+
*/
1031+
public function testIsWritableWithAsymmetricVisibility()
1032+
{
1033+
$object = new AsymmetricVisibility();
1034+
1035+
$this->assertTrue($this->propertyAccessor->isWritable($object, 'publicPublic'));
1036+
$this->assertFalse($this->propertyAccessor->isWritable($object, 'publicProtected'));
1037+
$this->assertFalse($this->propertyAccessor->isWritable($object, 'publicPrivate'));
1038+
$this->assertFalse($this->propertyAccessor->isWritable($object, 'privatePrivate'));
1039+
$this->assertFalse($this->propertyAccessor->isWritable($object, 'virtualNoSetHook'));
1040+
}
1041+
1042+
/**
1043+
* @requires PHP 8.4
1044+
*/
1045+
public function testIsReadableWithAsymmetricVisibility()
1046+
{
1047+
$object = new AsymmetricVisibility();
1048+
1049+
$this->assertTrue($this->propertyAccessor->isReadable($object, 'publicPublic'));
1050+
$this->assertTrue($this->propertyAccessor->isReadable($object, 'publicProtected'));
1051+
$this->assertTrue($this->propertyAccessor->isReadable($object, 'publicPrivate'));
1052+
$this->assertFalse($this->propertyAccessor->isReadable($object, 'privatePrivate'));
1053+
$this->assertTrue($this->propertyAccessor->isReadable($object, 'virtualNoSetHook'));
1054+
}
1055+
1056+
/**
1057+
* @requires PHP 8.4
1058+
*
1059+
* @dataProvider setValueWithAsymmetricVisibilityDataProvider
1060+
*/
1061+
public function testSetValueWithAsymmetricVisibility(string $propertyPath, ?string $expectedException)
1062+
{
1063+
$object = new AsymmetricVisibility();
1064+
1065+
if ($expectedException) {
1066+
$this->expectException($expectedException);
1067+
} else {
1068+
$this->expectNotToPerformAssertions();
1069+
}
1070+
1071+
$this->propertyAccessor->setValue($object, $propertyPath, true);
1072+
}
1073+
1074+
/**
1075+
* @return iterable<array{0: string, 1: null|class-string}>
1076+
*/
1077+
public static function setValueWithAsymmetricVisibilityDataProvider(): iterable
1078+
{
1079+
yield ['publicPublic', null];
1080+
yield ['publicProtected', \Error::class];
1081+
yield ['publicPrivate', \Error::class];
1082+
yield ['privatePrivate', NoSuchPropertyException::class];
1083+
yield ['virtualNoSetHook', \Error::class];
1084+
}
10261085
}

0 commit comments

Comments
 (0)