3v4l.org

run code in 300+ PHP versions simultaneously
<?php // This is the class that we want to make lazy. // Note that this is an opaque class from the PoV of the proxy-generation logic: // its implementation details are unknown. class A { protected array $data = []; public function __construct() { $this->data = ['foo' => 123]; } public function __get($n) { return $this->data[$n] ?? null; } // line 15 public function __set($n, $v) { $this->data[$n] = $v; } public function __isset($n) { return isset($this->data[$n]); } public function __unset($n) { unset($this->data[$n]); } } // This is a simplified proxy class that would typically be generated automatically // from the signature of the proxied class. The way this works is by unsetting all // declared properties at instantiation time, then using the magic methods to lazily // initialize them on demand. This is a very simple implementation that does not handle // all edge cases, but it should be enough to illustrate the issue. The logic in each // magic method is very repetitive: we checks if we're trying to access the uninitialized // property. If yes, we initialize it, if not, we call the parent method. class LazyA extends A { private const STATUS_UNINITIALIZED = 0; private const STATUS_INITIALIZING = 1; private const STATUS_INITIALIZED = 2; private $lazyStatus = self::STATUS_UNINITIALIZED; public function __construct() { unset($this->data); } public function __get($n) { if (self::STATUS_INITIALIZED === $this->lazyStatus || 'data' !== $n) { if ('data' === $n && (new ReflectionProperty($this, 'data'))->isInitialized($this)) { return $this->data; } return parent::__get($n); } $this->initialize(); return $this->data; } public function __set($n, $v) { if (self::STATUS_INITIALIZED === $this->lazyStatus || 'data' !== $n) { parent::__set($n, $v); } $this->initialize(); $this->data = $v; } public function __isset($n) { if (self::STATUS_INITIALIZED === $this->lazyStatus || 'data' !== $n) { return parent::__isset($n); } $this->initialize(); return isset($this->data); } public function __unset($n) { if (self::STATUS_INITIALIZED === $this->lazyStatus || 'data' !== $n) { parent::__unset($n); } $this->initialize(); unset($this->data); } private function initialize() { if (self::STATUS_INITIALIZING === $this->lazyStatus) { return; } $this->lazyStatus = self::STATUS_INITIALIZING; parent::__construct(); $this->lazyStatus = self::STATUS_INITIALIZED; } } $a = new LazyA(); // Currently, this will trigger a TypeError: // Cannot assign null to property A::$data of type array on line 15 // With Ilija's patch, this will work as expected and will display int(123) var_dump($a->foo);
Output for 8.1.0 - 8.1.30, 8.2.0 - 8.2.25, 8.3.0 - 8.3.13
int(123)

preferences:
55.3 ms | 406 KiB | 5 Q