Created
November 22, 2019 21:08
-
-
Save krmgns/c64c41fd2dd029efe0b72d04ff55a73d to your computer and use it in GitHub Desktop.
Weird behavior with uninitialized typed properties and __set/__get
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Link: https://bugs.php.net/bug.php?id=78859 | |
| --- | |
| Weird behavior with uninitialized typed properties and __set/__get | |
| Seems calling a constructor is triggering __set magic for uninitialized typed properties. So it does not matter the property is public or private/protected. | |
| I suppose the problem is __set/__get called before __construct when a type is given to a property. Also I if remove __get then I get object(acme\Options)#1 (1) { ["stack"]=> array(1) { ["stack"]=> array(1) { ["one"]=> int(1) } } }. | |
| Test script: | |
| final class Options { | |
| // This is OK but why redundant initialization? | |
| // public array $stack = []; | |
| public array $stack; | |
| public function __construct(array $stack) { | |
| $this->stack = $stack; | |
| } | |
| public function __set(string $name, $value) { | |
| // if (empty($this->stack)) { | |
| // // This line below yields: object(acme\Options)#1 (0) { ["stack"]=> uninitialized(array) }. | |
| // // return; | |
| // // This line below yields: object(acme\Options)#1 (1) { ["stack"]=> array(1) { ["stack"]=> array(1) { ["one"]=> int(1) } } }. | |
| // // $this->stack = []; | |
| // } | |
| // This is solving problem but the purpose is not that also corrupting $stack structure inserting a new sub-array. | |
| // object(acme\Options)#1 (1) { ["stack"]=> array(2) { ["one"]=> int(1) ["stack"]=> array(1) { ["one"]=> int(1) } } } | |
| // $this->stack = $value; | |
| // This is indicating that __set called before (before __construct). | |
| // throw new \Exception(); | |
| // This is problematic part. | |
| $this->stack[$name] = $value; | |
| } | |
| public function __get(string $name) { | |
| // This is indicating that __get called before (before __construct). | |
| // throw new \Exception(); | |
| return $this->stack[$name] ?? null; | |
| } | |
| } | |
| var_dump(new Options(['one' => 1])); | |
| Expected result: | |
| object(acme\Options)#1 (1) { | |
| ["stack"]=> | |
| array(1) { | |
| ["one"]=> | |
| int(1) | |
| } | |
| } | |
| Actual result: | |
| PHP Notice: Indirect modification of overloaded property acme\Options::$stack has no effect in /var/www/a.php on line 19 | |
| PHP Fatal error: Uncaught TypeError: Typed property acme\Options::$stack must be array, null used in /var/www/a.php:19 | |
| Stack trace: | |
| #0 /var/www/a.php(16): acme\Options->__set() | |
| #1 /var/www/a.php(26): acme\Options->__construct() | |
| #2 {main} | |
| thrown in /var/www/a.php on line 19 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment