Skip to content

Strange behavior of ReflectionClass::getProperty with trait and inheritance #15753

Closed
@HypeMC

Description

@HypeMC

Description

The following code: https://3v4l.org/Zbnqa

<?php

class ParentClass
{
    protected readonly int $value;
}

trait Foo
{
    protected readonly int $value;
}

class ChildClassWithoutTrait extends ParentClass
{
    protected readonly int $value;
}

$reflector = new ReflectionClass(ChildClassWithoutTrait::class);
$instance = $reflector->newInstanceWithoutConstructor();

var_dump(
    $reflector->hasProperty('value'),
    $prop = $reflector->getProperty('value')
);

$prop->setValue($instance, 20);

var_dump($instance);

echo '--------------------------------------------------------------------------------------------------------', PHP_EOL;

class ClassWithTraitAndNoParent
{
    use Foo;
}

$reflector = new ReflectionClass(ClassWithTraitAndNoParent::class);
$instance = $reflector->newInstanceWithoutConstructor();

var_dump(
    $reflector->hasProperty('value'),
    $prop = $reflector->getProperty('value')
);

$prop->setValue($instance, 20);

var_dump($instance);

echo '--------------------------------------------------------------------------------------------------------', PHP_EOL;

class ChildClassWithTrait extends ParentClass
{
    use Foo;
}

$reflector = new ReflectionClass(ChildClassWithTrait::class);
$instance = $reflector->newInstanceWithoutConstructor();

var_dump(
    $reflector->hasProperty('value'),
    $prop = $reflector->getProperty('value')
);

$prop->setValue($instance, 20);

var_dump($instance);

Resulted in this output:

bool(true)
object(ReflectionProperty)#3 (2) {
  ["name"]=>
  string(5) "value"
  ["class"]=>
  string(22) "ChildClassWithoutTrait"
}
object(ChildClassWithoutTrait)#2 (1) {
  ["value":protected]=>
  int(20)
}
--------------------------------------------------------------------------------------------------------
bool(true)
object(ReflectionProperty)#2 (2) {
  ["name"]=>
  string(5) "value"
  ["class"]=>
  string(25) "ClassWithTraitAndNoParent"
}
object(ClassWithTraitAndNoParent)#1 (1) {
  ["value":protected]=>
  int(20)
}
--------------------------------------------------------------------------------------------------------
bool(true)
object(ReflectionProperty)#1 (2) {
  ["name"]=>
  string(5) "value"
  ["class"]=>
  string(11) "ParentClass"
}

Fatal error: Uncaught Error: Cannot initialize readonly property ParentClass::$value from scope ChildClassWithTrait in /in/Zbnqa:64
Stack trace:
#0 /in/Zbnqa(64): ReflectionProperty->setValue(Object(ChildClassWithTrait), 20)
#1 {main}
  thrown in /in/Zbnqa on line 64

Process exited with code 255.

But I expected this output instead:

bool(true)
object(ReflectionProperty)#3 (2) {
  ["name"]=>
  string(5) "value"
  ["class"]=>
  string(22) "ChildClassWithoutTrait"
}
object(ChildClassWithoutTrait)#2 (1) {
  ["value":protected]=>
  int(20)
}
--------------------------------------------------------------------------------------------------------
bool(true)
object(ReflectionProperty)#2 (2) {
  ["name"]=>
  string(5) "value"
  ["class"]=>
  string(25) "ClassWithTraitAndNoParent"
}
object(ClassWithTraitAndNoParent)#1 (1) {
  ["value":protected]=>
  int(20)
}
--------------------------------------------------------------------------------------------------------
bool(true)
object(ReflectionProperty)#1 (2) {
  ["name"]=>
  string(5) "value"
  ["class"]=>
  string(11) "ChildClassWithTrait"
}
object(ChildClassWithTrait)#n (1) {
  ["value":protected]=>
  int(20)
}

It seems that a combination of traits and inheritance somehow breaks the getProperty() method. Even though the hasProperty() method returns true for ChildClassWithTrait, the getProperty() method returns a ReflectionProperty for ParentClass.

PHP Version

all

Operating System

No response

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions