8000 bug #54194 [PropertyAccess] Fix checking for missing properties (nico… · symfony/symfony@dc4f3fb · GitHub
[go: up one dir, main page]

Skip to content

Commit dc4f3fb

Browse files
bug #54194 [PropertyAccess] Fix checking for missing properties (nicolas-grekas)
This PR was merged into the 6.4 branch. Discussion ---------- [PropertyAccess] Fix checking for missing properties | Q | A | ------------- | --- | Branch? | 6.4 | Bug fix? | yes | New feature? | no | Deprecations? | no | Issues | Fix #48970, fix #54194, fix #51919 | License | MIT PropertyAccess and PropertyInfo make a few assumptions when magic methods are involved. Namely they assume that if a `__get` method is defined, then any property name is accessible. This assumption is generally wrong, and this becomes more evident with the introduction of lazy objects. The linked issue gives some more details. In this PR, I tweak these assumptions in order to make less of them. Note that there is no bullet-proof way to decide if a *virtual* property exists in PHP. We're missing an `__exists()` magic method for that. Because of this, I'm submitting this PR to 6.4 and not 5.4. Let 5.4 end its life quietly and ensure 6.4 works at its best with lazy ghosts, where they're mainstream. Commits ------- 9610a7c [PropertyAccess] Fix checking for missing properties
2 parents ad56682 + 9610a7c commit dc4f3fb

File tree

4 files changed

+29
-9
lines changed

4 files changed

+29
-9
lines changed

src/Symfony/Component/PropertyAccess/PropertyAccessor.php

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -411,8 +411,18 @@ private function readProperty(array $zval, string $property, bool $ignoreInvalid
411411
throw $e;
412412
}
413413
} elseif (PropertyReadInfo::TYPE_PROPERTY === $type) {
414-
if ($access->canBeReference() && !isset($object->$name) && !\array_key_exists($name, (array) $object) && !(new \ReflectionProperty($class, $name))->hasType()) {
415-
throw new UninitializedPropertyException(sprintf('The property "%s::$%s" is not initialized.', $class, $name));
414+
if (!isset($object->$name) && !\array_key_exists($name, (array) $object)) {
415+
try {
416+
$r = new \ReflectionProperty($class, $name);
417+
418+
if ($r->isPublic() && !$r->hasType()) {
419+
throw new UninitializedPropertyException(sprintf('The property "%s::$%s" is not initialized.', $class, $name));
420+
}
421+
} catch (\ReflectionException $e) {
422+
if (!$ignoreInvalidProperty) {
423+
throw new NoSuchPropertyException(sprintf('Can\'t get a way to read the property "%s" in class "%s".', $property, $class));
424+
}
425+
}
416426
}
417427

418428
$result[self::VALUE] = $object->$name;

src/Symfony/Component/PropertyAccess/Tests/Fixtures/TestClassMagicGet.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,4 +39,9 @@ public function __get(string $property)
3939
return 'constant value';
4040
}
4141
}
42+
43+
public function __isset(string $property)
44+
{
45+
return \in_array($property, ['magicProperty', 'constantMagicProperty'], true);
46+
}
4247
}

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -990,12 +990,19 @@ public function testGetValuePropertyThrowsExceptionIfUninitializedWithLazyGhost(
990990

991991
public function testGetValueGetterThrowsExceptionIfUninitializedWithLazyGhost()
992992
{
993+
$lazyGhost = $this->createUninitializedObjectPropertyGhost();
994+
993995
$this->expectException(UninitializedPropertyException::class);
994996
$this->expectExceptionMessage('The property "Symfony\Component\PropertyAccess\Tests\Fixtures\UninitializedObjectProperty::$privateUninitialized" is not readable because it is typed "DateTimeInterface". You should initialize it or declare a default value instead.');
995997

998+
$this->propertyAccessor->getValue($lazyGhost, 'privateUninitialized');
999+
}
1000+
1001+
public function testIsReadableWithMissingPropertyAndLazyGhost()
1002+
{
9961003
$lazyGhost = $this->createUninitializedObjectPropertyGhost();
9971004

998-
$this->propertyAccessor->getValue($lazyGhost, 'privateUninitialized');
1005+
$this->assertFalse($this->propertyAccessor->isReadable($lazyGhost, 'dummy'));
9991006
}
10001007

10011008
private function createUninitializedObjectPropertyGhost(): UninitializedObjectProperty

src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -274,14 +274,12 @@ public function getReadInfo(string $class, string $property, array $context = []
274274
return new PropertyReadInfo(PropertyReadInfo::TYPE_METHOD, $getsetter, $this->getReadVisiblityForMethod($method), $method->isStatic(), false);
275275
}
276276

277-
if ($allowMagicGet && $reflClass->hasMethod('__get') && ($reflClass->getMethod('__get')->getModifiers() & $this->methodReflectionFlags)) {
278-
return new PropertyReadInfo(PropertyReadInfo::TYPE_PROPERTY, $property, PropertyReadInfo::VISIBILITY_PUBLIC, false, false);
277+
if ($allowMagicGet && $reflClass->hasMethod('__get') && (($r = $reflClass->getMethod('__get'))->getModifiers() & $this->methodReflectionFlags)) {
278+
return new PropertyReadInfo(PropertyReadInfo::TYPE_PROPERTY, $property, PropertyReadInfo::VISIBILITY_PUBLIC, false, $r->returnsReference());
279279
}
280280

281-
if ($hasProperty && ($reflClass->getProperty($property)->getModifiers() & $this->propertyReflectionFlags)) {
282-
$reflProperty = $reflClass->getProperty($property);
283-
284-
return new PropertyReadInfo(PropertyReadInfo::TYPE_PROPERTY, $property, $this->getReadVisiblityForProperty($reflProperty), $reflProperty->isStatic(), true);
281+
if ($hasProperty && (($r = $reflClass->getProperty($property))->getModifiers() & $this->propertyReflectionFlags)) {
282+
return new PropertyReadInfo(PropertyReadInfo::TYPE_PROPERTY, $property, $this->getReadVisiblityForProperty($r), $r->isStatic(), true);
285283
}
286284

287285
if ($allowMagicCall && $reflClass->hasMethod('__call') && ($reflClass->getMethod('__call')->getModifiers() & $this->methodReflectionFlags)) {

0 commit comments

Comments
 (0)
0