dimanche 9 août 2020

PHP Reflection -- is this a bug or expected behavior?

I am creating a class that uses ReflectionProperty and am getting strange results.

Essentially, calling $reflectionProperty->getType()->getName() is returning the value of some entirely unrelated array. It does this under seemingly random conditions.

See below:

// this will be used as a type hinted property.
class DummyClass {
    public function __construct($arr) {}
}


// a class that sets its property values reflectively
class BaseClass {
    /** @var ReflectionProperty[] */
    private static $publicProps = [];

    /**
     * Gets public ReflectionProperties of the concrete class, and caches them
     * so we do not need to perform reflection again for this concrete class.
     *
     * @return ReflectionProperty[]
     * @throws ReflectionException
     */
    private function getPublicProps(){
        if (!static::$publicProps) {
            $concreteClass = get_class($this);
            static::$publicProps = (new ReflectionClass($concreteClass))
                ->getProperties(ReflectionProperty::IS_PUBLIC);
        }
        return static::$publicProps;
    }

    /**
     * For each public property in this class set value to the corresponding value from $propArr.
     *
     * @param $propArr
     * @throws ReflectionException
     */
    public function __construct($propArr) {
        $concreteClass = get_class($this);
        echo "Creating new instance of $concreteClass<br>";
        foreach ($this->getPublicProps() as $prop) {
            // get which property to set, its class, and value to pass to constructor
            $propName = $prop->getName();
            $propClass = $prop->getType()->getName();
            $propValue = $propArr[$propName];
            $propValueStr = var_export($propValue, true);
            // print out what we are about to do, and assert $propClass is correct.
            echo "---Setting: ->$propName = new $propClass($propValueStr)<br>";
            assert($propClass === "DummyClass", "$propClass !== DummyClass");
            // create the instance and assign it
            $refClass = new ReflectionClass($propClass);
            $this->$propName = $refClass->newInstanceArgs([$propValue]);
        }
    }
}

// a concrete implementation of the above class, with only 1 type hinted property.
class ConcreteClass extends BaseClass {
    public DummyClass $prop1;
}

// should create an instance of ConcreteClass
// with ->prop1 = new DummyClass(["foo"=>"abc123"])
$testArr1 = [
    "prop1" => ["foo" => "abc123"]
];
// should create an instance of ConcreteClass
// with ->prop1 = new DummyClass(["boo"=>"abc123def456"])
$testArr2 = [
    "prop1" => ["boo" => "abc123def456"]
];
$tc1 = new ConcreteClass($testArr1);
echo "Created TestClass1...<br><br>";
$tc2 = new ConcreteClass($testArr2);
echo "Created TestClass2...<br><br>";
die;

The results:

Creating new instance of ConcreteClass
Setting: ->prop1 = new DummyClass(array ( 'foo' => 'abc123', ))
Created TestClass1...

Creating new instance of ConcreteClass
Setting: ->prop1 = new abc123def456(array ( 'boo' => 'abc123def456', )) 
Error: assert(): abc123def456 !== DummyClass failed

Notice that the value of $propClass is abc123def456 -- how did that happen?

More Weirdness

  • Change the value of "abc123def456" to "12345678" and it will work.
  • Change the value of "abc123def456" to "123456789" and it will not work.
  • Omit the var_export(), and it will work. (Though, it may still break in other cases).

My gut tells me this is a PHP bug, but I might be doing something wrong, and/or this may be documented somewhere. I would like some clarification, because as of right now my only reliable solution is to not cache the reflected $publicProps. This results in an unnecessary call to ReflectionClass->getProperties() every single time I create a new ConcreteClass, which I'd like to avoid.





Aucun commentaire:

Enregistrer un commentaire