Lazy objects

RFC: https://wiki.php.net/rfc/lazy-objects

Closes GH-15019
This commit is contained in:
Arnaud Le Blanc 2023-05-19 12:55:05 +02:00
parent e12188fe89
commit 58aa6fc830
No known key found for this signature in database
GPG Key ID: 0098C05DD15ABC13
209 changed files with 13063 additions and 115 deletions

1
NEWS
View File

@ -5,6 +5,7 @@ PHP NEWS
- Core:
. Fixed bug GH-15330 (Do not scan generator frames more than once). (Arnaud)
. Fixed bug GH-15644 (Asymmetric visibility doesn't work with hooks). (ilutov)
. Implemented lazy objects RFC. (Arnaud)
- DOM:
. Fixed bug GH-13988 (Storing DOMElement consume 4 times more memory in

View File

@ -164,6 +164,20 @@ PHP 8.4 UPGRADE NOTES
. The DSN's credentials, when set, are given priority over their PDO
constructor counterparts, being closer to the documentation states.
- Reflection:
. Added methods ReflectionClass::newLazyGhost(),
ReflectionClass::newLazyProxy(), ReflectionClass::resetAsLazyGhost(),
ReflectionClass::resetAsLazyProxy(),
ReflectionClass::isUninitializedLazyObject(),
ReflectionClass::initializeLazyObject(),
ReflectionClass::markLazyObjectAsInitialized(),
ReflectionClass::getLazyInitializer(),
ReflectionProperty::skipLazyInitialization(),
ReflectionProperty::setRawValueWithoutLazyInitialization() and constants
ReflectionClass::SKIP_*.
If you have a method or constant with the same name, you might encounter
errors if the declaration is incompatible.
- SimpleXML:
. Get methods called, or casting to a string on a SimpleXMLElement will no
longer implicitly reset the iterator data, unless explicitly rewound.
@ -269,6 +283,8 @@ PHP 8.4 UPGRADE NOTES
See Zend/tests/use_function/ns_end_resets_seen_symbols_1.phpt.
. Implemented asymmetric property visibility.
RFC: https://wiki.php.net/rfc/asymmetric-visibility-v2
. Implemented lazy objects.
RFC: https://wiki.php.net/rfc/lazy-objects
- Curl:
. curl_version() returns an additional feature_list value, which is an
@ -393,6 +409,20 @@ PHP 8.4 UPGRADE NOTES
. ReflectionConstant was introduced.
. ReflectionClassConstant::isDeprecated() was introduced.
. ReflectionGenerator::isClosed() was introduced.
. Multiple methods and constants related to lazy objects were introduced:
- ReflectionClass::newLazyGhost()
- ReflectionClass::newLazyProxy()
- ReflectionClass::resetAsLazyGhost()
- ReflectionClass::resetAsLazyProxy()
- ReflectionClass::isUninitializedLazyObject()
- ReflectionClass::initializeLazyObject()
- ReflectionClass::markLazyObjectAsInitialized()
- ReflectionClass::getLazyInitializer()
- ReflectionProperty::skipLazyInitialization()
- ReflectionProperty::setRawValueWithoutLazyInitialization()
- ReflectionClass::SKIP_INITIALIZATION_ON_SERIALIZE
- ReflectionClass::SKIP_DESTRUCTOR
RFC: https://wiki.php.net/rfc/lazy-objects
- Standard:
. stream_bucket_make_writeable() and stream_bucket_new() will now return a
@ -780,6 +810,20 @@ PHP 8.4 UPGRADE NOTES
. Added pg_set_chunked_rows_size to allow to fetch results in chunk of
max N rows.
- Reflection:
. Multiple methods related to lazy objects were introduced:
- ReflectionClass::newLazyGhost()
- ReflectionClass::newLazyProxy()
- ReflectionClass::resetAsLazyGhost()
- ReflectionClass::resetAsLazyProxy()
- ReflectionClass::isUninitializedLazyObject()
- ReflectionClass::initializeLazyObject()
- ReflectionClass::markLazyObjectAsInitialized()
- ReflectionClass::getLazyInitializer()
- ReflectionProperty::skipLazyInitialization()
- ReflectionProperty::setRawValueWithoutLazyInitialization()
RFC: https://wiki.php.net/rfc/lazy-objects
- Sodium:
. Added the sodium_crypto_aead_aegis128l_*() and sodium_crypto_aead_aegis256l_*()
functions to support the AEGIS family of authenticated encryption algorithms,

View File

@ -99,6 +99,9 @@ PHP 8.4 INTERNALS UPGRADE NOTES
zend_std_get_properties(). Use zend_std_get_properties_ex() or
zend_std_get_properties() instead.
* zend_object.properties must not be accessed directly. Use
zend_std_get_properties_ex() instead.
* Removed IS_STATIC_VAR_UNINITIALIZED constant. Check for IS_NULL in the
static_variables array instead.

View File

@ -0,0 +1,77 @@
--TEST--
Lazy objects: clone calls __clone() once
--FILE--
<?php
class C {
public $a = 1;
public function __construct() {
}
public function __clone() {
var_dump("clone");
}
}
function test(string $name, object $obj) {
printf("# %s:\n", $name);
$reflector = new ReflectionClass($obj::class);
$clone = clone $obj;
var_dump($reflector->isUninitializedLazyObject($obj));
var_dump($obj);
var_dump($reflector->isUninitializedLazyObject($clone));
var_dump($clone);
}
$reflector = new ReflectionClass(C::class);
$obj = $reflector->newLazyGhost(function ($obj) {
var_dump("initializer");
$obj->__construct();
});
test('Ghost', $obj);
$obj = $reflector->newLazyProxy(function ($obj) {
var_dump("initializer");
return new C();
});
test('Proxy', $obj);
--EXPECTF--
# Ghost:
string(11) "initializer"
string(5) "clone"
bool(false)
object(C)#%d (1) {
["a"]=>
int(1)
}
bool(false)
object(C)#%d (1) {
["a"]=>
int(1)
}
# Proxy:
string(11) "initializer"
string(5) "clone"
bool(false)
lazy proxy object(C)#%d (1) {
["instance"]=>
object(C)#%d (1) {
["a"]=>
int(1)
}
}
bool(false)
lazy proxy object(C)#%d (1) {
["instance"]=>
object(C)#%d (1) {
["a"]=>
int(1)
}
}

View File

@ -0,0 +1,35 @@
--TEST--
Lazy objects: clone is independant of the original object
--FILE--
<?php
class SomeObj {
public string $foo = 'A';
public string $dummy;
}
$reflector = new ReflectionClass(SomeObj::class);
$predefinedObject = new SomeObj();
$initializer = function () use ($predefinedObject) {
return $predefinedObject;
};
$myProxy = $reflector->newLazyProxy($initializer);
$reflector->getProperty('foo')->skipLazyInitialization($myProxy);
$clonedProxy = clone $myProxy;
var_dump($clonedProxy->foo);
$reflector->initializeLazyObject($myProxy);
$myProxy->foo = 'B';
$reflector->initializeLazyObject($clonedProxy);
var_dump($myProxy->foo);
var_dump($clonedProxy->foo);
--EXPECT--
string(1) "A"
string(1) "B"
string(1) "A"

View File

@ -0,0 +1,56 @@
--TEST--
Lazy objects: clone is independant of the original object
--FILE--
<?php
class SomeObj {
public Value $value;
public function __construct() {
$this->value = new Value();
}
public function __clone() {
$this->value = clone $this->value;
}
}
class Value {
public string $value = 'A';
}
function test(string $name, object $obj) {
printf("# %s:\n", $name);
$reflector = new ReflectionClass(SomeObj::class);
$clonedObj = clone $obj;
var_dump($clonedObj->value->value);
$reflector->initializeLazyObject($obj);
$obj->value->value = 'B';
$reflector->initializeLazyObject($clonedObj);
var_dump($obj->value->value);
var_dump($clonedObj->value->value);
}
$reflector = new ReflectionClass(SomeObj::class);
test('Ghost', $reflector->newLazyGhost(function ($obj) {
$obj->__construct();
}));
test('Proxy', $reflector->newLazyProxy(function () {
return new SomeObj();
}));
?>
--EXPECT--
# Ghost:
string(1) "A"
string(1) "B"
string(1) "A"
# Proxy:
string(1) "A"
string(1) "B"
string(1) "A"

View File

@ -0,0 +1,40 @@
--TEST--
Lazy objects: clone is independant of the original object
--FILE--
<?php
class SomeObj {
public string $foo = 'X';
public string $dummy;
}
function test(string $name, object $obj) {
printf("# %s:\n", $name);
$reflector = new ReflectionClass(SomeObj::class);
$clonedObj = clone $obj;
$reflector->initializeLazyObject($obj);
$reflector->getProperty('foo')->setRawValueWithoutLazyInitialization($clonedObj, 'Y');
$reflector->initializeLazyObject($clonedObj);
var_dump($clonedObj->foo);
}
$reflector = new ReflectionClass(SomeObj::class);
test('Ghost', $reflector->newLazyGhost(function ($obj) {
}));
test('Proxy', $reflector->newLazyProxy(function () {
return new SomeObj();
}));
?>
--EXPECT--
# Ghost:
string(1) "Y"
# Proxy:
string(1) "Y"

View File

@ -0,0 +1,73 @@
--TEST--
Lazy objects: clone of initialized lazy object does not initialize twice
--FILE--
<?php
class C {
public $a = 1;
public function __construct() {
}
}
function test(string $name, object $obj) {
printf("# %s:\n", $name);
$reflector = new ReflectionClass($obj::class);
$reflector->initializeLazyObject($obj);
$clone = clone $obj;
var_dump($reflector->isUninitializedLazyObject($obj));
var_dump($obj);
var_dump($reflector->isUninitializedLazyObject($clone));
var_dump($clone);
}
$reflector = new ReflectionClass(C::class);
$obj = $reflector->newLazyGhost(function ($obj) {
var_dump("initializer");
$obj->__construct();
});
test('Ghost', $obj);
$obj = $reflector->newLazyProxy(function ($obj) {
var_dump("initializer");
return new C();
});
test('Proxy', $obj);
--EXPECTF--
# Ghost:
string(11) "initializer"
bool(false)
object(C)#%d (1) {
["a"]=>
int(1)
}
bool(false)
object(C)#%d (1) {
["a"]=>
int(1)
}
# Proxy:
string(11) "initializer"
bool(false)
lazy proxy object(C)#%d (1) {
["instance"]=>
object(C)#%d (1) {
["a"]=>
int(1)
}
}
bool(false)
lazy proxy object(C)#%d (1) {
["instance"]=>
object(C)#%d (1) {
["a"]=>
int(1)
}
}

View File

@ -0,0 +1,56 @@
--TEST--
Lazy objects: clone: initializer exception
--FILE--
<?php
class C {
public $a = 1;
public function __construct() {
}
public function __destruct() {
var_dump(__METHOD__);
}
}
function test(string $name, object $obj) {
printf("# %s:\n", $name);
$reflector = new ReflectionClass($obj::class);
try {
$clone = clone $obj;
} catch (\Exception $e) {
printf("%s: %s\n", $e::class, $e->getMessage());
}
var_dump($reflector->isUninitializedLazyObject($obj));
var_dump($obj);
}
$reflector = new ReflectionClass(C::class);
$obj = $reflector->newLazyGhost(function ($obj) {
throw new \Exception('initializer');
});
test('Ghost', $obj);
$obj = $reflector->newLazyProxy(function ($obj) {
throw new \Exception('initializer');
});
test('Proxy', $obj);
--EXPECTF--
# Ghost:
Exception: initializer
bool(true)
lazy ghost object(C)#%d (0) {
}
# Proxy:
Exception: initializer
bool(true)
lazy proxy object(C)#%d (0) {
}

View File

@ -0,0 +1,71 @@
--TEST--
Lazy objects: clone initializes object
--FILE--
<?php
class C {
public $a = 1;
public function __construct() {
}
}
function test(string $name, object $obj) {
printf("# %s:\n", $name);
$reflector = new ReflectionClass($obj::class);
$clone = clone $obj;
var_dump($reflector->isUninitializedLazyObject($obj));
var_dump($obj);
var_dump($reflector->isUninitializedLazyObject($clone));
var_dump($clone);
}
$reflector = new ReflectionClass(C::class);
$obj = $reflector->newLazyGhost(function ($obj) {
var_dump("initializer");
$obj->__construct();
});
test('Ghost', $obj);
$obj = $reflector->newLazyProxy(function ($obj) {
var_dump("initializer");
return new C();
});
test('Proxy', $obj);
--EXPECTF--
# Ghost:
string(11) "initializer"
bool(false)
object(C)#%d (1) {
["a"]=>
int(1)
}
bool(false)
object(C)#%d (1) {
["a"]=>
int(1)
}
# Proxy:
string(11) "initializer"
bool(false)
lazy proxy object(C)#%d (1) {
["instance"]=>
object(C)#%d (1) {
["a"]=>
int(1)
}
}
bool(false)
lazy proxy object(C)#%d (1) {
["instance"]=>
object(C)#%d (1) {
["a"]=>
int(1)
}
}

View File

@ -0,0 +1,36 @@
--TEST--
Lazy objects: clone returns an object of the same class
--FILE--
<?php
class A {
public function __construct(
public string $property,
) {}
}
class B extends A {
public function foo() { }
}
function only_b(B $b) { $b->foo(); }
$r = new ReflectionClass(B::class);
$b = $r->newLazyProxy(function ($obj) {
return new A('value');
});
$b->property = 'init_please';
$clone = clone $b;
only_b($b);
only_b($clone);
var_dump($b::class);
var_dump($clone::class);
?>
==DONE==
--EXPECT--
string(1) "B"
string(1) "B"
==DONE==

View File

@ -0,0 +1,93 @@
--TEST--
Lazy objects: Convertion to array
--FILE--
<?php
class C {
public int $a;
public $b;
public function __construct() {
$this->a = 1;
$this->b = 2;
}
}
function test(string $name, object $obj) {
printf("# %s\n", $name);
$reflector = new ReflectionClass(C::class);
$reflector->getProperty('a')->setRawValueWithoutLazyInitialization($obj, 3);
$a = [];
// Converts $obj to array internally
array_splice($a, 0, 0, $obj);
var_dump($a, $obj);
$reflector->initializeLazyObject($obj);
$a = [];
array_splice($a, 0, 0, $obj);
var_dump($a, $obj);
}
$reflector = new ReflectionClass(C::class);
$obj = $reflector->newLazyGhost(function ($obj) {
$obj->__construct();
});
test('Ghost', $obj);
$obj = $reflector->newLazyProxy(function () {
return new C();
});
test('Proxy', $obj);
?>
--EXPECTF--
# Ghost
array(1) {
[0]=>
int(3)
}
lazy ghost object(C)#%d (1) {
["a"]=>
int(3)
}
array(2) {
[0]=>
int(1)
[1]=>
int(2)
}
object(C)#%d (2) {
["a"]=>
int(1)
["b"]=>
int(2)
}
# Proxy
array(1) {
[0]=>
int(3)
}
lazy proxy object(C)#%d (1) {
["a"]=>
int(3)
}
array(2) {
[0]=>
int(1)
[1]=>
int(2)
}
lazy proxy object(C)#%d (1) {
["instance"]=>
object(C)#%d (2) {
["a"]=>
int(1)
["b"]=>
int(2)
}
}

View File

@ -0,0 +1,66 @@
--TEST--
Lazy objects: destructor of initialized objets is called
--FILE--
<?php
class C {
public int $a = 1;
public function __destruct() {
var_dump(__METHOD__, $this);
}
}
function ghost() {
$reflector = new ReflectionClass(C::class);
print "# Ghost:\n";
print "In makeLazy\n";
$obj = $reflector->newLazyGhost(function () {
var_dump("initializer");
});
print "After makeLazy\n";
var_dump($obj->a);
}
function proxy() {
$reflector = new ReflectionClass(C::class);
print "# Proxy:\n";
print "In makeLazy\n";
$obj = $reflector->newLazyProxy(function () {
var_dump("initializer");
return new C();
});
print "After makeLazy\n";
var_dump($obj->a);
}
ghost();
proxy();
--EXPECTF--
# Ghost:
In makeLazy
After makeLazy
string(11) "initializer"
int(1)
string(13) "C::__destruct"
object(C)#%d (1) {
["a"]=>
int(1)
}
# Proxy:
In makeLazy
After makeLazy
string(11) "initializer"
int(1)
string(13) "C::__destruct"
object(C)#%d (1) {
["a"]=>
int(1)
}

View File

@ -0,0 +1,43 @@
--TEST--
Lazy objects: destructor of lazy objets is not called if not initialized
--FILE--
<?php
class C {
public $a;
public function __destruct() {
var_dump(__METHOD__);
}
}
$reflector = new ReflectionClass(C::class);
print "# Ghost:\n";
print "In newLazyGhost\n";
$obj = $reflector->newLazyGhost(function () {
var_dump("initializer");
});
print "After newLazyGhost\n";
// Does not call destructor
$obj = null;
print "# Proxy:\n";
print "In newLazyProxy\n";
$obj = $reflector->newLazyProxy(function () {
var_dump("initializer");
});
print "After newLazyGhost\n";
// Does not call destructor
$obj = null;
--EXPECT--
# Ghost:
In newLazyGhost
After newLazyGhost
# Proxy:
In newLazyProxy
After newLazyGhost

View File

@ -0,0 +1,63 @@
--TEST--
Lazy objects: property fetch coalesce initializes object
--FILE--
<?php
class C {
public int $a = 1;
public function __construct() {
var_dump(__METHOD__);
}
}
function test(string $name, object $obj) {
printf("# %s:\n", $name);
var_dump($obj);
var_dump($obj->a ?? null);
var_dump($obj);
}
$reflector = new ReflectionClass(C::class);
$obj = $reflector->newLazyGhost(function ($obj) {
var_dump("initializer");
$obj->__construct();
});
test('Ghost', $obj);
$obj = $reflector->newLazyProxy(function ($obj) {
var_dump("initializer");
return new C();
});
test('Proxy', $obj);
--EXPECTF--
# Ghost:
lazy ghost object(C)#%d (0) {
["a"]=>
uninitialized(int)
}
string(11) "initializer"
string(14) "C::__construct"
int(1)
object(C)#%d (1) {
["a"]=>
int(1)
}
# Proxy:
lazy proxy object(C)#%d (0) {
["a"]=>
uninitialized(int)
}
string(11) "initializer"
string(14) "C::__construct"
int(1)
lazy proxy object(C)#%d (1) {
["instance"]=>
object(C)#%d (1) {
["a"]=>
int(1)
}
}

View File

@ -0,0 +1,63 @@
--TEST--
Lazy objects: property fetch coalesce on non existing property initializes object
--FILE--
<?php
class C {
public int $a = 1;
public function __construct() {
var_dump(__METHOD__);
}
}
function test(string $name, object $obj) {
printf("# %s:\n", $name);
var_dump($obj);
var_dump($obj->unknown ?? null);
var_dump($obj);
}
$reflector = new ReflectionClass(C::class);
$obj = $reflector->newLazyGhost(function ($obj) {
var_dump("initializer");
$obj->__construct();
});
test('Ghost', $obj);
$obj = $reflector->newLazyProxy(function ($obj) {
var_dump("initializer");
return new C();
});
test('Proxy', $obj);
--EXPECTF--
# Ghost:
lazy ghost object(C)#%d (0) {
["a"]=>
uninitialized(int)
}
string(11) "initializer"
string(14) "C::__construct"
NULL
object(C)#%d (1) {
["a"]=>
int(1)
}
# Proxy:
lazy proxy object(C)#%d (0) {
["a"]=>
uninitialized(int)
}
string(11) "initializer"
string(14) "C::__construct"
NULL
lazy proxy object(C)#%d (1) {
["instance"]=>
object(C)#%d (1) {
["a"]=>
int(1)
}
}

View File

@ -0,0 +1,72 @@
--TEST--
Lazy objects: property fetch initializes object
--FILE--
<?php
class C {
public $a;
public int $b = 1;
public function __construct(int $a) {
var_dump(__METHOD__);
$this->a = $a;
$this->b = 2;
}
}
function test(string $name, object $obj) {
printf("# %s:\n", $name);
var_dump($obj);
var_dump($obj->a);
var_dump($obj);
}
$reflector = new ReflectionClass(C::class);
$obj = $reflector->newLazyGhost(function ($obj) {
var_dump("initializer");
$obj->__construct(1);
});
test('Ghost', $obj);
$obj = $reflector->newLazyProxy(function ($obj) {
var_dump("initializer");
return new C(1);
});
test('Proxy', $obj);
--EXPECTF--
# Ghost:
lazy ghost object(C)#%d (0) {
["b"]=>
uninitialized(int)
}
string(11) "initializer"
string(14) "C::__construct"
int(1)
object(C)#%d (2) {
["a"]=>
int(1)
["b"]=>
int(2)
}
# Proxy:
lazy proxy object(C)#%d (0) {
["b"]=>
uninitialized(int)
}
string(11) "initializer"
string(14) "C::__construct"
int(1)
lazy proxy object(C)#%d (1) {
["instance"]=>
object(C)#%d (2) {
["a"]=>
int(1)
["b"]=>
int(2)
}
}

View File

@ -0,0 +1,65 @@
--TEST--
Lazy objects: property fetch of dynamic property initializes object
--FILE--
<?php
#[AllowDynamicProperties]
class C {
public int $a;
public function __construct() {
var_dump(__METHOD__);
}
}
function test(string $name, object $obj) {
printf("# %s:\n", $name);
var_dump($obj);
var_dump(@$obj->dynamic);
var_dump($obj);
}
$reflector = new ReflectionClass(C::class);
$obj = $reflector->newLazyGhost(function ($obj) {
var_dump("initializer");
$obj->__construct();
});
test('Ghost', $obj);
$obj = $reflector->newLazyProxy(function ($obj) {
var_dump("initializer");
return new C();
});
test('Proxy', $obj);
--EXPECTF--
# Ghost:
lazy ghost object(C)#%d (0) {
["a"]=>
uninitialized(int)
}
string(11) "initializer"
string(14) "C::__construct"
NULL
object(C)#%d (0) {
["a"]=>
uninitialized(int)
}
# Proxy:
lazy proxy object(C)#%d (0) {
["a"]=>
uninitialized(int)
}
string(11) "initializer"
string(14) "C::__construct"
NULL
lazy proxy object(C)#%d (1) {
["instance"]=>
object(C)#%d (0) {
["a"]=>
uninitialized(int)
}
}

View File

@ -0,0 +1,75 @@
--TEST--
Lazy objects: hooked property fetch does not initialize object if hook does not observe object state
--FILE--
<?php
class C {
public $a {
get { return $this->a; }
set($value) { $this->a = $value; }
}
public int $b = 1;
public function __construct(int $a) {
var_dump(__METHOD__);
$this->a = $a;
$this->b = 2;
}
}
function test(string $name, object $obj) {
printf("# %s:\n", $name);
var_dump($obj);
var_dump($obj->a);
var_dump($obj);
}
$reflector = new ReflectionClass(C::class);
$obj = $reflector->newLazyGhost(function ($obj) {
var_dump("initializer");
$obj->__construct(1);
});
test('Ghost', $obj);
$obj = $reflector->newLazyProxy(function ($obj) {
var_dump("initializer");
return new C(1);
});
test('Proxy', $obj);
--EXPECTF--
# Ghost:
lazy ghost object(C)#%d (0) {
["b"]=>
uninitialized(int)
}
string(11) "initializer"
string(14) "C::__construct"
int(1)
object(C)#%d (2) {
["a"]=>
int(1)
["b"]=>
int(2)
}
# Proxy:
lazy proxy object(C)#%d (0) {
["b"]=>
uninitialized(int)
}
string(11) "initializer"
string(14) "C::__construct"
int(1)
lazy proxy object(C)#%d (1) {
["instance"]=>
object(C)#%d (2) {
["a"]=>
int(1)
["b"]=>
int(2)
}
}

View File

@ -0,0 +1,76 @@
--TEST--
Lazy objects: virtual hooked property fetch may initialize object if hook observes object state
--FILE--
<?php
class C {
public $_a;
public $a {
&get { return $this->_a; }
}
public int $b = 1;
public function __construct(int $a) {
var_dump(__METHOD__);
$this->_a = $a;
$this->b = 2;
}
}
function test(string $name, object $obj) {
printf("# %s:\n", $name);
var_dump($obj);
$a = &$obj->a;
var_dump($a);
var_dump($obj);
}
$reflector = new ReflectionClass(C::class);
$obj = $reflector->newLazyGhost(function ($obj) {
var_dump("initializer");
$obj->__construct(1);
});
test('Ghost', $obj);
$obj = $reflector->newLazyProxy(function ($obj) {
var_dump("initializer");
return new C(1);
});
test('Proxy', $obj);
--EXPECTF--
# Ghost:
lazy ghost object(C)#%d (0) {
["b"]=>
uninitialized(int)
}
string(11) "initializer"
string(14) "C::__construct"
int(1)
object(C)#%d (2) {
["_a"]=>
&int(1)
["b"]=>
int(2)
}
# Proxy:
lazy proxy object(C)#%d (0) {
["b"]=>
uninitialized(int)
}
string(11) "initializer"
string(14) "C::__construct"
int(1)
lazy proxy object(C)#%d (1) {
["instance"]=>
object(C)#%d (2) {
["_a"]=>
&int(1)
["b"]=>
int(2)
}
}

View File

@ -0,0 +1,62 @@
--TEST--
Lazy objects: virtual hooked property fetch does not initialize object if hook does not observe object state
--FILE--
<?php
class C {
public $a {
get { return 1; }
}
public int $b = 1;
public function __construct(int $a) {
var_dump(__METHOD__);
$this->b = 2;
}
}
function test(string $name, object $obj) {
printf("# %s:\n", $name);
var_dump($obj);
var_dump($obj->a);
var_dump($obj);
}
$reflector = new ReflectionClass(C::class);
$obj = $reflector->newLazyGhost(function ($obj) {
var_dump("initializer");
$obj->__construct(1);
});
test('Ghost', $obj);
$obj = $reflector->newLazyProxy(function ($obj) {
var_dump("initializer");
return new C(1);
});
test('Proxy', $obj);
--EXPECTF--
# Ghost:
lazy ghost object(C)#%d (0) {
["b"]=>
uninitialized(int)
}
int(1)
lazy ghost object(C)#%d (0) {
["b"]=>
uninitialized(int)
}
# Proxy:
lazy proxy object(C)#%d (0) {
["b"]=>
uninitialized(int)
}
int(1)
lazy proxy object(C)#%d (0) {
["b"]=>
uninitialized(int)
}

View File

@ -0,0 +1,66 @@
--TEST--
Lazy objects: magic property fetch initializes object if magic method observes object state
--FILE--
<?php
class C {
public int $a = 1;
public function __construct() {
var_dump(__METHOD__);
}
public function __get($name) {
return $this->a;
}
}
function test(string $name, object $obj) {
printf("# %s:\n", $name);
var_dump($obj);
var_dump($obj->magic);
var_dump($obj);
}
$reflector = new ReflectionClass(C::class);
$obj = $reflector->newLazyGhost(function ($obj) {
var_dump("initializer");
$obj->__construct();
});
test('Ghost', $obj);
$obj = $reflector->newLazyProxy(function ($obj) {
var_dump("initializer");
return new C();
});
test('Proxy', $obj);
--EXPECTF--
# Ghost:
lazy ghost object(C)#%d (0) {
["a"]=>
uninitialized(int)
}
string(11) "initializer"
string(14) "C::__construct"
int(1)
object(C)#%d (1) {
["a"]=>
int(1)
}
# Proxy:
lazy proxy object(C)#%d (0) {
["a"]=>
uninitialized(int)
}
string(11) "initializer"
string(14) "C::__construct"
int(1)
lazy proxy object(C)#%d (1) {
["instance"]=>
object(C)#%d (1) {
["a"]=>
int(1)
}
}

View File

@ -0,0 +1,60 @@
--TEST--
Lazy objects: magic property fetch does not not initialize object if magic method does not observe object state
--FILE--
<?php
class C {
public int $a = 1;
public function __construct() {
var_dump(__METHOD__);
}
public function __get($name) {
return $name;
}
}
function test(string $name, object $obj) {
printf("# %s:\n", $name);
var_dump($obj);
var_dump($obj->magic);
var_dump($obj);
}
$reflector = new ReflectionClass(C::class);
$obj = $reflector->newLazyGhost(function ($obj) {
var_dump("initializer");
$obj->__construct();
});
test('Ghost', $obj);
$obj = $reflector->newLazyProxy(function ($obj) {
var_dump("initializer");
return new C();
});
test('Proxy', $obj);
--EXPECTF--
# Ghost:
lazy ghost object(C)#%d (0) {
["a"]=>
uninitialized(int)
}
string(5) "magic"
lazy ghost object(C)#%d (0) {
["a"]=>
uninitialized(int)
}
# Proxy:
lazy proxy object(C)#%d (0) {
["a"]=>
uninitialized(int)
}
string(5) "magic"
lazy proxy object(C)#%d (0) {
["a"]=>
uninitialized(int)
}

View File

@ -0,0 +1,70 @@
--TEST--
Lazy objects: recursive magic property fetch initializes object if magic method observes object state
--FILE--
<?php
class C {
public int $a = 1;
public function __construct() {
var_dump(__METHOD__);
}
public function __get($name) {
return $this->$name;
}
}
function test(string $name, object $obj) {
printf("# %s:\n", $name);
var_dump($obj);
var_dump($obj->magic);
var_dump($obj);
}
$reflector = new ReflectionClass(C::class);
$obj = $reflector->newLazyGhost(function ($obj) {
var_dump("initializer");
$obj->__construct();
});
test('Ghost', $obj);
$obj = $reflector->newLazyProxy(function ($obj) {
var_dump("initializer");
return new C();
});
test('Proxy', $obj);
--EXPECTF--
# Ghost:
lazy ghost object(C)#%d (0) {
["a"]=>
uninitialized(int)
}
string(11) "initializer"
string(14) "C::__construct"
Warning: Undefined property: C::$magic in %s on line %d
NULL
object(C)#%d (1) {
["a"]=>
int(1)
}
# Proxy:
lazy proxy object(C)#%d (0) {
["a"]=>
uninitialized(int)
}
string(11) "initializer"
string(14) "C::__construct"
Warning: Undefined property: C::$magic in %s on line %d
NULL
lazy proxy object(C)#%d (1) {
["instance"]=>
object(C)#%d (1) {
["a"]=>
int(1)
}
}

View File

@ -0,0 +1,62 @@
--TEST--
Lazy objects: dynamic property op error
--FILE--
<?php
#[AllowDynamicProperties]
class C {
public int $a = 1;
public function __construct() {
var_dump(__METHOD__);
}
}
function test(string $name, object $obj) {
printf("# %s:\n", $name);
var_dump($obj);
try {
var_dump(@$obj->dynamic++);
} catch(Error $e) {
printf("%s: %s\n", $e::class, $e->getMessage());
}
var_dump($obj);
}
$reflector = new ReflectionClass(C::class);
$obj = $reflector->newLazyGhost(function ($obj) {
throw new Error("initializer");
$obj->__construct();
});
test('Ghost', $obj);
$obj = $reflector->newLazyProxy(function ($obj) {
throw new Error("initializer");
});
test('Proxy', $obj);
--EXPECTF--
# Ghost:
lazy ghost object(C)#%d (0) {
["a"]=>
uninitialized(int)
}
Error: initializer
lazy ghost object(C)#%d (0) {
["a"]=>
uninitialized(int)
}
# Proxy:
lazy proxy object(C)#%d (0) {
["a"]=>
uninitialized(int)
}
Error: initializer
lazy proxy object(C)#%d (0) {
["a"]=>
uninitialized(int)
}

View File

@ -0,0 +1,69 @@
--TEST--
Lazy objects: dynamic property op initializes object
--FILE--
<?php
#[AllowDynamicProperties]
class C {
public int $a = 1;
public function __construct() {
var_dump(__METHOD__);
}
}
function test(string $name, object $obj) {
printf("# %s:\n", $name);
var_dump($obj);
var_dump(@$obj->dynamic++);
var_dump($obj);
}
$reflector = new ReflectionClass(C::class);
$obj = $reflector->newLazyGhost(function ($obj) {
var_dump("initializer");
$obj->__construct();
});
test('Ghost', $obj);
$obj = $reflector->newLazyProxy(function ($obj) {
var_dump("initializer");
return new C();
});
test('Proxy', $obj);
--EXPECTF--
# Ghost:
lazy ghost object(C)#%d (0) {
["a"]=>
uninitialized(int)
}
string(11) "initializer"
string(14) "C::__construct"
NULL
object(C)#%d (2) {
["a"]=>
int(1)
["dynamic"]=>
int(1)
}
# Proxy:
lazy proxy object(C)#%d (0) {
["a"]=>
uninitialized(int)
}
string(11) "initializer"
string(14) "C::__construct"
NULL
lazy proxy object(C)#%d (1) {
["instance"]=>
object(C)#%d (2) {
["a"]=>
int(1)
["dynamic"]=>
int(1)
}
}

View File

@ -0,0 +1,62 @@
--TEST--
Lazy objects: property op error
--FILE--
<?php
class C {
public int $a = 1;
public function __construct() {
var_dump(__METHOD__);
$this->a = 2;
}
}
function test(string $name, object $obj) {
printf("# %s:\n", $name);
var_dump($obj);
try {
var_dump($obj->a++);
} catch (Error $e) {
printf("%s: %s\n", $e::class, $e->getMessage());
}
var_dump($obj);
}
$reflector = new ReflectionClass(C::class);
$obj = $reflector->newLazyGhost(function ($obj) {
throw new Error("initializer");
});
test('Ghost', $obj);
$obj = $reflector->newLazyProxy(function ($obj) {
throw new Error("initializer");
});
test('Proxy', $obj);
--EXPECTF--
# Ghost:
lazy ghost object(C)#%d (0) {
["a"]=>
uninitialized(int)
}
Error: initializer
lazy ghost object(C)#%d (0) {
["a"]=>
uninitialized(int)
}
# Proxy:
lazy proxy object(C)#%d (0) {
["a"]=>
uninitialized(int)
}
Error: initializer
lazy proxy object(C)#%d (0) {
["a"]=>
uninitialized(int)
}

View File

@ -0,0 +1,65 @@
--TEST--
Lazy objects: property op initializes object
--FILE--
<?php
class C {
public int $a = 1;
public function __construct() {
var_dump(__METHOD__);
$this->a = 2;
}
}
function test(string $name, object $obj) {
printf("# %s:\n", $name);
var_dump($obj);
var_dump($obj->a++);
var_dump($obj);
}
$reflector = new ReflectionClass(C::class);
$obj = $reflector->newLazyGhost(function ($obj) {
var_dump("initializer");
$obj->__construct();
});
test('Ghost', $obj);
$obj = $reflector->newLazyProxy(function ($obj) {
var_dump("initializer");
return new C();
});
test('Proxy', $obj);
--EXPECTF--
# Ghost:
lazy ghost object(C)#%d (0) {
["a"]=>
uninitialized(int)
}
string(11) "initializer"
string(14) "C::__construct"
int(2)
object(C)#%d (1) {
["a"]=>
int(3)
}
# Proxy:
lazy proxy object(C)#%d (0) {
["a"]=>
uninitialized(int)
}
string(11) "initializer"
string(14) "C::__construct"
int(2)
lazy proxy object(C)#%d (1) {
["instance"]=>
object(C)#%s (1) {
["a"]=>
int(3)
}
}

View File

@ -0,0 +1,91 @@
--TEST--
Lazy objects: property op on skipped property does not initialize object
--FILE--
<?php
class C {
public $a;
public int $b = 1;
public int $c;
public function __construct() {
var_dump(__METHOD__);
$this->a = 2;
}
}
function test(string $name, object $obj) {
printf("# %s:\n", $name);
$reflector = new ReflectionClass($obj);
$reflector->getProperty('a')->skipLazyInitialization($obj);
$reflector->getProperty('b')->skipLazyInitialization($obj);
$reflector->getProperty('c')->skipLazyInitialization($obj);
var_dump($obj);
var_dump($obj->a++);
var_dump($obj->b++);
try {
var_dump($obj->c++);
} catch (Error $e) {
printf("%s\n", $e->getMessage());
}
var_dump($obj);
}
$reflector = new ReflectionClass(C::class);
$obj = $reflector->newLazyGhost(function ($obj) {
var_dump("initializer");
$obj->__construct();
});
test('Ghost', $obj);
$obj = $reflector->newLazyProxy(function ($obj) {
var_dump("initializer");
return new c();
});
test('Proxy', $obj);
--EXPECTF--
# Ghost:
object(C)#%d (2) {
["a"]=>
NULL
["b"]=>
int(1)
["c"]=>
uninitialized(int)
}
NULL
int(1)
Typed property C::$c must not be accessed before initialization
object(C)#%d (2) {
["a"]=>
int(1)
["b"]=>
int(2)
["c"]=>
uninitialized(int)
}
# Proxy:
object(C)#%d (2) {
["a"]=>
NULL
["b"]=>
int(1)
["c"]=>
uninitialized(int)
}
NULL
int(1)
Typed property C::$c must not be accessed before initialization
object(C)#%d (2) {
["a"]=>
int(1)
["b"]=>
int(2)
["c"]=>
uninitialized(int)
}

View File

@ -0,0 +1,65 @@
--TEST--
Lazy objects: property fetch ref initializes object
--FILE--
<?php
class C {
public int $a = 1;
public function __construct() {
var_dump(__METHOD__);
}
}
function test(string $name, object $obj) {
printf("# %s:\n", $name);
var_dump($obj);
$ref = &$obj->a;
var_dump($ref);
var_dump($obj);
}
$reflector = new ReflectionClass(C::class);
$obj = $reflector->newLazyGhost(function ($obj) {
var_dump("initializer");
$obj->__construct();
});
test('Ghost', $obj);
$obj = $reflector->newLazyProxy(function ($obj) {
var_dump("initializer");
return new C();
});
test('Proxy', $obj);
--EXPECTF--
# Ghost:
lazy ghost object(C)#%d (0) {
["a"]=>
uninitialized(int)
}
string(11) "initializer"
string(14) "C::__construct"
int(1)
object(C)#%d (1) {
["a"]=>
&int(1)
}
# Proxy:
lazy proxy object(C)#%d (0) {
["a"]=>
uninitialized(int)
}
string(11) "initializer"
string(14) "C::__construct"
int(1)
lazy proxy object(C)#%d (1) {
["instance"]=>
object(C)#%d (1) {
["a"]=>
&int(1)
}
}

View File

@ -0,0 +1,87 @@
--TEST--
Lazy objects: fetch ref on skipped property does not initialize object
--FILE--
<?php
class C {
public $a;
public int $b = 1;
public int $c;
public function __construct() {
var_dump(__METHOD__);
}
}
function test(string $name, object $obj) {
printf("# %s:\n", $name);
var_dump($obj);
$ref = &$obj->a;
$ref = &$obj->b;
try {
$ref = &$obj->c;
} catch (Error $e) {
printf("%s\n", $e->getMessage());
}
var_dump($ref);
var_dump($obj);
}
$reflector = new ReflectionClass(C::class);
$obj = $reflector->newLazyGhost(function ($obj) {
var_dump("initializer");
$obj->__construct();
});
test('Ghost', $obj);
$obj = $reflector->newLazyProxy(function ($obj) {
var_dump("initializer");
return new C();
});
test('Proxy', $obj);
--EXPECTF--
# Ghost:
lazy ghost object(C)#%d (0) {
["b"]=>
uninitialized(int)
["c"]=>
uninitialized(int)
}
string(11) "initializer"
string(14) "C::__construct"
Cannot access uninitialized non-nullable property C::$c by reference
int(1)
object(C)#%d (2) {
["a"]=>
NULL
["b"]=>
&int(1)
["c"]=>
uninitialized(int)
}
# Proxy:
lazy proxy object(C)#%d (0) {
["b"]=>
uninitialized(int)
["c"]=>
uninitialized(int)
}
string(11) "initializer"
string(14) "C::__construct"
Cannot access uninitialized non-nullable property C::$c by reference
int(1)
lazy proxy object(C)#%d (1) {
["instance"]=>
object(C)#%d (2) {
["a"]=>
NULL
["b"]=>
&int(1)
["c"]=>
uninitialized(int)
}
}

View File

@ -0,0 +1,93 @@
--TEST--
Lazy objects: fetch skipped property does not initialize object
--FILE--
<?php
class C {
public $a;
public int $b = 1;
public int $c;
public function __construct(int $a) {
var_dump(__METHOD__);
$this->a = $a;
$this->b = 2;
}
}
function test(string $name, object $obj) {
printf("# %s:\n", $name);
$reflector = new ReflectionClass($obj);
$reflector->getProperty('a')->skipLazyInitialization($obj);
$reflector->getProperty('b')->skipLazyInitialization($obj);
$reflector->getProperty('c')->skipLazyInitialization($obj);
var_dump($obj);
var_dump($obj->a);
var_dump($obj->b);
try {
var_dump($obj->c);
} catch (Error $e) {
printf("%s\n", $e->getMessage());
}
var_dump($obj);
}
$reflector = new ReflectionClass(C::class);
$obj = $reflector->newLazyGhost(function ($obj) {
var_dump("initializer");
$obj->__construct(1);
});
test('Ghost', $obj);
$obj = $reflector->newLazyProxy(function ($obj) {
var_dump("initializer");
return new C(1);
});
test('Proxy', $obj);
--EXPECTF--
# Ghost:
object(C)#%d (2) {
["a"]=>
NULL
["b"]=>
int(1)
["c"]=>
uninitialized(int)
}
NULL
int(1)
Typed property C::$c must not be accessed before initialization
object(C)#%d (2) {
["a"]=>
NULL
["b"]=>
int(1)
["c"]=>
uninitialized(int)
}
# Proxy:
object(C)#%d (2) {
["a"]=>
NULL
["b"]=>
int(1)
["c"]=>
uninitialized(int)
}
NULL
int(1)
Typed property C::$c must not be accessed before initialization
object(C)#%d (2) {
["a"]=>
NULL
["b"]=>
int(1)
["c"]=>
uninitialized(int)
}

View File

@ -0,0 +1,65 @@
--TEST--
Lazy objects: final classes can be initialized lazily
--FILE--
<?php
final class C {
public int $a;
public function __construct() {
var_dump(__METHOD__);
$this->a = 1;
}
}
$reflector = new ReflectionClass(C::class);
print "# Ghost:\n";
$obj = $reflector->newLazyGhost(function ($obj) {
var_dump("initializer");
$obj->__construct();
});
var_dump($obj);
var_dump($obj->a);
var_dump($obj);
print "# Proxy:\n";
$obj = $reflector->newLazyProxy(function ($obj) {
var_dump("initializer");
return new C();
});
var_dump($obj);
var_dump($obj->a);
var_dump($obj);
--EXPECTF--
# Ghost:
lazy ghost object(C)#%d (0) {
["a"]=>
uninitialized(int)
}
string(11) "initializer"
string(14) "C::__construct"
int(1)
object(C)#%d (1) {
["a"]=>
int(1)
}
# Proxy:
lazy proxy object(C)#%d (0) {
["a"]=>
uninitialized(int)
}
string(11) "initializer"
string(14) "C::__construct"
int(1)
lazy proxy object(C)#%d (1) {
["instance"]=>
object(C)#%d (1) {
["a"]=>
int(1)
}
}

View File

@ -0,0 +1,54 @@
--TEST--
Lazy objects: GC 001
--FILE--
<?php
class Canary {
public function __destruct() {
var_dump(__FUNCTION__);
}
}
class C {
}
function ghost() {
printf("# Ghost:\n");
$canary = new Canary();
$obj = (new ReflectionClass(C::class))->newInstanceWithoutConstructor();
(new ReflectionClass($obj))->resetAsLazyGhost($obj, function () use ($canary) {
});
$canary = null;
$obj = null;
gc_collect_cycles();
}
function proxy() {
printf("# Proxy:\n");
$canary = new Canary();
$obj = (new ReflectionClass(C::class))->newInstanceWithoutConstructor();
(new ReflectionClass($obj))->resetAsLazyProxy($obj, function () use ($canary) {
return new C();
});
$canary = null;
$obj = null;
gc_collect_cycles();
}
ghost();
proxy();
?>
==DONE==
--EXPECT--
# Ghost:
string(10) "__destruct"
# Proxy:
string(10) "__destruct"
==DONE==

View File

@ -0,0 +1,59 @@
--TEST--
Lazy objects: GC 002
--FILE--
<?php
class Canary {
public $value;
public function __destruct() {
var_dump(__FUNCTION__);
}
}
class C {
}
function ghost() {
printf("# Ghost:\n");
$canary = new Canary();
$obj = (new ReflectionClass(C::class))->newInstanceWithoutConstructor();
(new ReflectionClass($obj))->resetAsLazyGhost($obj, function () use ($canary) {
});
$canary->value = $obj;
$canary = null;
$obj = null;
gc_collect_cycles();
}
function proxy() {
printf("# Proxy:\n");
$canary = new Canary();
$obj = (new ReflectionClass(C::class))->newInstanceWithoutConstructor();
(new ReflectionClass($obj))->resetAsLazyProxy($obj, function () use ($canary) {
return new C();
});
$canary->value = $obj;
$canary = null;
$obj = null;
gc_collect_cycles();
}
ghost();
proxy();
?>
==DONE==
--EXPECT--
# Ghost:
string(10) "__destruct"
# Proxy:
string(10) "__destruct"
==DONE==

View File

@ -0,0 +1,59 @@
--TEST--
Lazy objects: GC 003
--FILE--
<?php
class Canary {
public $value;
public function __destruct() {
var_dump(__FUNCTION__);
}
}
class C {
}
function ghost() {
printf("# Ghost:\n");
$canary = new Canary();
$obj = (new ReflectionClass(C::class))->newInstanceWithoutConstructor();
(new ReflectionClass($obj))->resetAsLazyGhost($obj, function () use ($canary) {
});
$canary->value = $obj;
$obj = null;
$canary = null;
gc_collect_cycles();
}
function proxy() {
printf("# Proxy:\n");
$canary = new Canary();
$obj = (new ReflectionClass(C::class))->newInstanceWithoutConstructor();
(new ReflectionClass($obj))->resetAsLazyProxy($obj, function () use ($canary) {
return new C();
});
$canary->value = $obj;
$obj = null;
$canary = null;
gc_collect_cycles();
}
ghost();
proxy();
?>
==DONE==
--EXPECT--
# Ghost:
string(10) "__destruct"
# Proxy:
string(10) "__destruct"
==DONE==

View File

@ -0,0 +1,67 @@
--TEST--
Lazy objects: GC 004
--FILE--
<?php
class Canary {
public $value;
public function __destruct() {
var_dump(__FUNCTION__);
}
}
class C {
}
function ghost() {
printf("# Ghost:\n");
$canary = new Canary();
$obj = (new ReflectionClass(C::class))->newInstanceWithoutConstructor();
(new ReflectionClass($obj))->resetAsLazyGhost($obj, function () use ($canary) {
});
var_dump($obj); // initializes property hash
$canary->value = $obj;
$obj = null;
$canary = null;
gc_collect_cycles();
}
function proxy() {
printf("# Proxy:\n");
$canary = new Canary();
$obj = (new ReflectionClass(C::class))->newInstanceWithoutConstructor();
(new ReflectionClass($obj))->resetAsLazyProxy($obj, function () use ($canary) {
return new C();
});
var_dump($obj); // initializes property hash
$canary->value = $obj;
$obj = null;
$canary = null;
gc_collect_cycles();
}
ghost();
proxy();
?>
==DONE==
--EXPECTF--
# Ghost:
object(C)#%d (0) {
}
string(10) "__destruct"
# Proxy:
object(C)#%d (0) {
}
string(10) "__destruct"
==DONE==

View File

@ -0,0 +1,68 @@
--TEST--
Lazy objects: GC 005
--FILE--
<?php
class Canary {
public $value;
public function __destruct() {
var_dump(__FUNCTION__);
}
}
class C {
public $value;
}
function ghost() {
printf("# Ghost:\n");
$canary = new Canary();
$reflector = new ReflectionClass(C::class);
$obj = $reflector->newInstanceWithoutConstructor();
$reflector->resetAsLazyGhost($obj, function () use ($canary) {
});
$reflector->getProperty('value')->setRawValueWithoutLazyInitialization($obj, $obj);
$reflector = null;
$canary->value = $obj;
$obj = null;
$canary = null;
gc_collect_cycles();
}
function proxy() {
printf("# Proxy:\n");
$canary = new Canary();
$reflector = new ReflectionClass(C::class);
$obj = $reflector->newInstanceWithoutConstructor();
$reflector->resetAsLazyProxy($obj, function () use ($canary) {
return new C();
});
$reflector->getProperty('value')->setRawValueWithoutLazyInitialization($obj, $obj);
$reflector = null;
$canary->value = $obj;
$obj = null;
$canary = null;
gc_collect_cycles();
}
ghost();
proxy();
?>
==DONE==
--EXPECT--
# Ghost:
string(10) "__destruct"
# Proxy:
string(10) "__destruct"
==DONE==

View File

@ -0,0 +1,36 @@
--TEST--
Lazy objects: GC 006
--FILE--
<?php
class Foo {
public $foo;
}
class Initializer {
public function __invoke($obj) {
$obj->foo = $this;
var_dump(__METHOD__);
}
public function __destruct() {
var_dump(__METHOD__);
}
}
$reflector = new ReflectionClass(Foo::class);
$foo = $reflector->newLazyGhost(new Initializer());
print "Dump\n";
var_dump($foo->foo);
print "Done\n";
?>
--EXPECTF--
Dump
string(21) "Initializer::__invoke"
object(Initializer)#%d (0) {
}
Done
string(23) "Initializer::__destruct"

View File

@ -0,0 +1,52 @@
--TEST--
Lazy objects: getLazyInitializer() returns initializer
--FILE--
<?php
class C {
public $a;
public static function initStatic() {}
public function init() {}
}
function foo() {
}
$reflector = new ReflectionClass(C::class);
$initializers = [
'foo',
foo(...),
function () {},
[C::class, 'initStatic'],
[new C(), 'init'],
C::initStatic(...),
(new C())->init(...),
];
foreach ($initializers as $i => $initializer) {
$c = $reflector->newLazyGhost($initializer);
if ($reflector->getLazyInitializer($c) !== $initializer) {
printf("Initializer %d: failed\n", $i);
continue;
}
$reflector->initializeLazyObject($c);
if ($reflector->getLazyInitializer($c) !== null) {
printf("Initializer %d: failed\n", $i);
continue;
}
printf("Initializer %d: ok\n", $i);
}
?>
--EXPECT--
Initializer 0: ok
Initializer 1: ok
Initializer 2: ok
Initializer 3: ok
Initializer 4: ok
Initializer 5: ok
Initializer 6: ok

View File

@ -0,0 +1,27 @@
--TEST--
Lazy objects: get_properties failure
--FILE--
<?php
class C {
public int $a;
public $b;
}
$reflector = new ReflectionClass(C::class);
$a = $reflector->newLazyProxy(function () {
throw new \Exception('Initializer');
});
$b = new C();
try {
var_dump($a > $b);
} catch (Exception $e) {
printf("%s: %s\n", $e::class, $e->getMessage());
}
?>
--EXPECT--
Exception: Initializer

View File

@ -0,0 +1,27 @@
--TEST--
Lazy objects: Class constants are updated before initialization
--FILE--
<?php
class C {
public stdClass $a = FOO;
}
$reflector = new ReflectionClass(C::class);
$c = $reflector->newLazyGhost(function () { });
function f() {
define('FOO', new stdClass);
}
f();
try {
var_dump($c->a);
} catch (\Error $e) {
printf("%s: %s\n", $e::class, $e->getMessage());
}
--EXPECTF--
object(stdClass)#%d (0) {
}

View File

@ -0,0 +1,26 @@
--TEST--
Lazy objects: Class constants are updated before initialization: update constant failure
--FILE--
<?php
class C {
public C $a = FOO;
}
$reflector = new ReflectionClass(C::class);
$c = $reflector->newLazyGhost(function () { });
function f() {
define('FOO', new stdClass);
}
f();
try {
var_dump($c->a);
} catch (\Error $e) {
printf("%s: %s\n", $e::class, $e->getMessage());
}
--EXPECT--
TypeError: Cannot assign stdClass to property C::$a of type C

View File

@ -0,0 +1,29 @@
--TEST--
Lazy objects: init exception 001
--FILE--
<?php
#[AllowDynamicProperties]
class C {
public int $a;
public $b;
}
$reflector = new ReflectionClass(C::class);
for ($i = 0; $i < 2; $i++) {
$obj = $reflector->newLazyGhost(function ($obj) use ($i) {
if ($i === 1) {
throw new \Exception();
}
});
$obj->c = 1;
}
?>
--EXPECTF--
Fatal error: Uncaught Exception in %s:%d
Stack trace:
#0 %s(%d): {closure:%s:%d}(Object(C))
#1 {main}
thrown in %s on line %d

View File

@ -0,0 +1,53 @@
--TEST--
Lazy objects: Object is still lazy after initializer exception
--FILE--
<?php
class C {
public $a = 1;
public int $b = 2;
}
function test(string $name, object $obj) {
$reflector = new ReflectionClass(C::class);
printf("# %s:\n", $name);
try {
$reflector->initializeLazyObject($obj);
} catch (Exception $e) {
printf("%s\n", $e->getMessage());
}
printf("Is lazy: %d\n", $reflector->isUninitializedLazyObject($obj));
}
$reflector = new ReflectionClass(C::class);
$obj = $reflector->newLazyGhost(function ($obj) {
var_dump("initializer");
$obj->a = 3;
$obj->b = 4;
throw new Exception('initializer exception');
});
test('Ghost', $obj);
$obj = $reflector->newLazyProxy(function ($obj) {
var_dump("initializer");
$obj->a = 3;
$obj->b = 4;
throw new Exception('initializer exception');
});
test('Proxy', $obj);
--EXPECT--
# Ghost:
string(11) "initializer"
initializer exception
Is lazy: 1
# Proxy:
string(11) "initializer"
initializer exception
Is lazy: 1

View File

@ -0,0 +1,74 @@
--TEST--
Lazy objects: Initializer effects are reverted after exception
--FILE--
<?php
class C {
public $a = 1;
public int $b = 2;
public int $c;
}
function test(string $name, object $obj) {
$reflector = new ReflectionClass(C::class);
printf("# %s:\n", $name);
(new ReflectionProperty(C::class, 'c'))->setRawValueWithoutLazyInitialization($obj, 0);
try {
$reflector->initializeLazyObject($obj);
} catch (Exception $e) {
printf("%s\n", $e->getMessage());
}
var_dump($obj);
printf("Is lazy: %d\n", $reflector->isUninitializedLazyObject($obj));
}
$reflector = new ReflectionClass(C::class);
$obj = $reflector->newLazyGhost(function ($obj) {
var_dump("initializer");
$obj->a = 3;
$obj->b = 4;
$obj->c = 5;
throw new Exception('initializer exception');
});
test('Ghost', $obj);
$obj = $reflector->newLazyProxy(function ($obj) {
var_dump("initializer");
$obj->a = 3;
$obj->b = 4;
$obj->c = 5;
throw new Exception('initializer exception');
});
// Initializer effects on the proxy are not reverted
test('Proxy', $obj);
--EXPECTF--
# Ghost:
string(11) "initializer"
initializer exception
lazy ghost object(C)#%d (1) {
["b"]=>
uninitialized(int)
["c"]=>
int(0)
}
Is lazy: 1
# Proxy:
string(11) "initializer"
initializer exception
lazy proxy object(C)#%d (3) {
["a"]=>
int(3)
["b"]=>
int(4)
["c"]=>
int(5)
}
Is lazy: 1

View File

@ -0,0 +1,79 @@
--TEST--
Lazy objects: Initializer effects are reverted after exception (dynamic properties)
--FILE--
<?php
#[AllowDynamicProperties]
class C {
public $a = 1;
public int $b = 2;
public int $c;
}
function test(string $name, object $obj) {
$reflector = new ReflectionClass(C::class);
printf("# %s:\n", $name);
(new ReflectionProperty(C::class, 'c'))->setRawValueWithoutLazyInitialization($obj, 0);
try {
$reflector->initializeLazyObject($obj);
} catch (Exception $e) {
printf("%s\n", $e->getMessage());
}
var_dump($obj);
printf("Is lazy: %d\n", $reflector->isUninitializedLazyObject($obj));
}
$reflector = new ReflectionClass(C::class);
$obj = $reflector->newLazyGhost(function ($obj) {
var_dump("initializer");
$obj->a = 3;
$obj->b = 4;
$obj->c = 5;
$obj->d = 6;
throw new Exception('initializer exception');
});
test('Ghost', $obj);
$obj = $reflector->newLazyProxy(function ($obj) {
var_dump("initializer");
$obj->a = 3;
$obj->b = 4;
$obj->c = 5;
$obj->d = 6;
throw new Exception('initializer exception');
});
// Initializer effects on the proxy are not reverted
test('Proxy', $obj);
--EXPECTF--
# Ghost:
string(11) "initializer"
initializer exception
lazy ghost object(C)#%d (1) {
["b"]=>
uninitialized(int)
["c"]=>
int(0)
}
Is lazy: 1
# Proxy:
string(11) "initializer"
initializer exception
lazy proxy object(C)#%d (4) {
["a"]=>
int(3)
["b"]=>
int(4)
["c"]=>
int(5)
["d"]=>
int(6)
}
Is lazy: 1

View File

@ -0,0 +1,86 @@
--TEST--
Lazy objects: Initializer effects are reverted after exception (dynamic properties, initialized hashtable)
--FILE--
<?php
#[AllowDynamicProperties]
class C {
public $a = 1;
public int $b = 2;
public int $c;
}
function test(string $name, object $obj) {
$reflector = new ReflectionClass(C::class);
printf("# %s:\n", $name);
// Builds properties hashtable
var_dump(get_mangled_object_vars($obj));
(new ReflectionProperty(C::class, 'c'))->setRawValueWithoutLazyInitialization($obj, 0);
try {
$reflector->initializeLazyObject($obj);
} catch (Exception $e) {
printf("%s\n", $e->getMessage());
}
var_dump($obj);
printf("Is lazy: %d\n", $reflector->isUninitializedLazyObject($obj));
}
$reflector = new ReflectionClass(C::class);
$obj = $reflector->newLazyGhost(function ($obj) {
var_dump("initializer");
$obj->a = 3;
$obj->b = 4;
$obj->c = 5;
$obj->d = 6;
throw new Exception('initializer exception');
});
test('Ghost', $obj);
$obj = $reflector->newLazyProxy(function ($obj) {
var_dump("initializer");
$obj->a = 3;
$obj->b = 4;
$obj->c = 5;
$obj->d = 6;
throw new Exception('initializer exception');
});
// Initializer effects on the proxy are not reverted
test('Proxy', $obj);
--EXPECTF--
# Ghost:
array(0) {
}
string(11) "initializer"
initializer exception
lazy ghost object(C)#%d (1) {
["b"]=>
uninitialized(int)
["c"]=>
int(0)
}
Is lazy: 1
# Proxy:
array(0) {
}
string(11) "initializer"
initializer exception
lazy proxy object(C)#%d (4) {
["a"]=>
int(3)
["b"]=>
int(4)
["c"]=>
int(5)
["d"]=>
int(6)
}
Is lazy: 1

View File

@ -0,0 +1,50 @@
--TEST--
Lazy objects: Initializer effects are reverted after exception (nested)
--FILE--
<?php
class A {
public $a;
}
class B {
public $b;
}
class C {
public $c;
}
$aReflector = new ReflectionClass(A::class);
$bReflector = new ReflectionClass(B::class);
$cReflector = new ReflectionClass(C::class);
$c = $cReflector->newLazyGhost(function ($c) {
$c->c = 1;
});
$b = $bReflector->newLazyGhost(function () {
throw new \Exception('xxx');
});
$a = $aReflector->newLazyGhost(function ($a) use ($b, $c) {
$a->a = $c->c + $b->b;
});
try {
$a->init = 'please';
} catch (\Exception $e) {
printf("%s: %s\n", $e::class, $e->getMessage());
}
var_dump($a, $b, $c);
?>
--EXPECTF--
Exception: xxx
lazy ghost object(A)#%d (0) {
}
lazy ghost object(B)#%d (0) {
}
object(C)#%d (1) {
["c"]=>
int(1)
}

View File

@ -0,0 +1,60 @@
--TEST--
Lazy objects: Object is still lazy after initializer exception (overridden prop)
--FILE--
<?php
class B {
public int $b = 1;
}
class C extends B {
public $a = 1;
public int $b = 2;
}
function test(string $name, object $obj) {
$reflector = new ReflectionClass(C::class);
printf("# %s:\n", $name);
$reflector->getProperty('b')->skipLazyInitialization($obj);
$i = 5;
$obj->b = &$i;
try {
$reflector->initializeLazyObject($obj);
} catch (Exception $e) {
printf("%s\n", $e->getMessage());
}
printf("Is lazy: %d\n", $reflector->isUninitializedLazyObject($obj));
}
$reflector = new ReflectionClass(C::class);
$obj = $reflector->newLazyGhost(function ($obj) {
var_dump("initializer");
$obj->a = 3;
$obj->b = 4;
throw new Exception('initializer exception');
});
test('Ghost', $obj);
$obj = $reflector->newLazyProxy(function ($obj) {
var_dump("initializer");
$obj->a = 3;
$obj->b = 4;
throw new Exception('initializer exception');
});
test('Proxy', $obj);
--EXPECT--
# Ghost:
string(11) "initializer"
initializer exception
Is lazy: 1
# Proxy:
string(11) "initializer"
initializer exception
Is lazy: 1

View File

@ -0,0 +1,85 @@
--TEST--
Lazy objects: Initializer effects are reverted after exception (properties hashtable)
--FILE--
<?php
class C {
public $a = 1;
public int $b = 2;
public int $c;
}
function test(string $name, object $obj) {
$reflector = new ReflectionClass(C::class);
printf("# %s:\n", $name);
(new ReflectionProperty(C::class, 'c'))->setRawValueWithoutLazyInitialization($obj, 0);
// Builds properties hashtable
var_dump(get_mangled_object_vars($obj));
try {
$reflector->initializeLazyObject($obj);
} catch (Exception $e) {
printf("%s\n", $e->getMessage());
}
var_dump($obj);
printf("Is lazy: %d\n", $reflector->isUninitializedLazyObject($obj));
}
$reflector = new ReflectionClass(C::class);
$obj = $reflector->newLazyGhost(function ($obj) {
var_dump("initializer");
$obj->a = 3;
$obj->b = 4;
$obj->c = 5;
throw new Exception('initializer exception');
});
test('Ghost', $obj);
$obj = $reflector->newLazyProxy(function ($obj) {
var_dump("initializer");
$obj->a = 3;
$obj->b = 4;
$obj->c = 5;
throw new Exception('initializer exception');
});
// Initializer effects on the proxy are not reverted
test('Proxy', $obj);
--EXPECTF--
# Ghost:
array(1) {
["c"]=>
int(0)
}
string(11) "initializer"
initializer exception
lazy ghost object(C)#%d (1) {
["b"]=>
uninitialized(int)
["c"]=>
int(0)
}
Is lazy: 1
# Proxy:
array(1) {
["c"]=>
int(0)
}
string(11) "initializer"
initializer exception
lazy proxy object(C)#%d (3) {
["a"]=>
int(3)
["b"]=>
int(4)
["c"]=>
int(5)
}
Is lazy: 1

View File

@ -0,0 +1,97 @@
--TEST--
Lazy objects: Initializer effects are reverted after exception (properties hashtable referenced after initializer)
--FILE--
<?php
class C {
public $a = 1;
public int $b = 2;
public int $c;
}
function test(string $name, object $obj) {
$reflector = new ReflectionClass(C::class);
printf("# %s:\n", $name);
(new ReflectionProperty(C::class, 'c'))->setRawValueWithoutLazyInitialization($obj, 0);
// Builds properties hashtable
var_dump(get_mangled_object_vars($obj));
try {
$reflector->initializeLazyObject($obj);
} catch (Exception $e) {
printf("%s\n", $e->getMessage());
}
var_dump($obj);
printf("Is lazy: %d\n", $reflector->isUninitializedLazyObject($obj));
var_dump($table);
}
$reflector = new ReflectionClass(C::class);
$obj = $reflector->newLazyGhost(function ($obj) {
global $table;
var_dump("initializer");
$obj->a = 3;
$obj->b = 4;
$obj->c = 5;
$table = (array) $obj;
throw new Exception('initializer exception');
});
test('Ghost', $obj);
$obj = $reflector->newLazyProxy(function ($obj) {
global $table;
var_dump("initializer");
$obj->a = 3;
$obj->b = 4;
$obj->c = 5;
$table = (array) $obj;
throw new Exception('initializer exception');
});
// Initializer effects on the proxy are not reverted
test('Proxy', $obj);
--EXPECTF--
# Ghost:
array(1) {
["c"]=>
int(0)
}
string(11) "initializer"
initializer exception
lazy ghost object(C)#%d (1) {
["b"]=>
uninitialized(int)
["c"]=>
int(0)
}
Is lazy: 1
Warning: Undefined variable $table in %s on line %d
NULL
# Proxy:
array(1) {
["c"]=>
int(0)
}
string(11) "initializer"
initializer exception
lazy proxy object(C)#%d (3) {
["a"]=>
int(3)
["b"]=>
int(4)
["c"]=>
int(5)
}
Is lazy: 1
Warning: Undefined variable $table in %s on line %d
NULL

View File

@ -0,0 +1,35 @@
--TEST--
Lazy objects: fatal error during initialization of ghost object
--FILE--
<?php
class C {
public $a;
public $b;
public $c;
public function __construct() {
eval('{');
}
}
$reflector = new ReflectionClass(C::class);
$obj = $reflector->newLazyGhost(function ($obj) {
var_dump("initializer");
$obj->__construct();
});
$reflector->getProperty('b')->setRawValueWithoutLazyInitialization($obj, new stdClass);
var_dump($obj);
var_dump($obj->c);
var_dump($obj);
--EXPECTF--
lazy ghost object(C)#%d (1) {
["b"]=>
object(stdClass)#%d (0) {
}
}
string(11) "initializer"
Parse error: Unclosed '{' in %s on line %d

View File

@ -0,0 +1,100 @@
--TEST--
Lazy objects: Pre-initialization reference source types are properly handled (no initialization exception)
--FILE--
<?php
class C {
public ?C $a;
public ?C $b;
public $c;
public function __construct() {
$this->a = null;
unset($this->b);
$this->b = null;
}
}
function test(string $name, object $obj) {
$reflector = new ReflectionClass(C::class);
printf("# %s:\n", $name);
$reflector->getProperty('a')->setRawValueWithoutLazyInitialization($obj, null);
$refA = &$obj->a;
$reflector->getProperty('b')->setRawValueWithoutLazyInitialization($obj, null);
$refB = &$obj->b;
var_dump($obj);
var_dump($obj->c);
var_dump($obj);
try {
// $refA retained its reference source type (except for the proxy
// case: its the responsibility of the initializer to propagate
// pre-initialized properties to the instance)
$refA = 1;
} catch (\Error $e) {
printf("%s: %s\n", $e::class, $e->getMessage());
}
// source type was not duplicated
unset($obj->a);
$refA = 1;
$refB = 1;
}
$reflector = new ReflectionClass(C::class);
$obj = $reflector->newLazyGhost(function ($obj) {
var_dump("initializer");
$obj->__construct();
});
test('Ghost', $obj);
$obj = $reflector->newLazyProxy(function ($obj) {
var_dump("initializer");
return new C(null);
});
test('Proxy', $obj);
--EXPECTF--
# Ghost:
lazy ghost object(C)#%d (2) {
["a"]=>
&NULL
["b"]=>
&NULL
}
string(11) "initializer"
NULL
object(C)#%d (3) {
["a"]=>
&NULL
["b"]=>
NULL
["c"]=>
NULL
}
TypeError: Cannot assign int to reference held by property C::$a of type ?C
# Proxy:
lazy proxy object(C)#%d (2) {
["a"]=>
&NULL
["b"]=>
&NULL
}
string(11) "initializer"
NULL
lazy proxy object(C)#%d (1) {
["instance"]=>
object(C)#%d (3) {
["a"]=>
NULL
["b"]=>
NULL
["c"]=>
NULL
}
}

View File

@ -0,0 +1,109 @@
--TEST--
Lazy objects: Pre-initialization reference source types are properly handled after initializer exception
--FILE--
<?php
class C {
public ?C $a;
public ?C $b;
public $c;
public function __construct() {
unset($this->b);
throw new \Exception('initializer exception');
}
}
function test(string $name, object $obj) {
$reflector = new ReflectionClass(C::class);
printf("# %s:\n", $name);
$reflector->getProperty('a')->setRawValueWithoutLazyInitialization($obj, null);
$refA = &$obj->a;
$reflector->getProperty('b')->setRawValueWithoutLazyInitialization($obj, null);
$refB = &$obj->b;
var_dump($obj);
try {
var_dump($obj->c);
} catch (\Exception $e) {
printf("%s: %s\n", $e::class, $e->getMessage());
}
var_dump($obj);
try {
// $refA retained its reference source type (except for the proxy
// case: it is the responsibility of the initializer to propagate
// pre-initialized properties to the instance)
$refA = 1;
} catch (\Error $e) {
printf("%s: %s\n", $e::class, $e->getMessage());
}
// source type was not duplicated
unset($obj->a);
$refA = 1;
try {
// $refB retained its reference source type
$refB = 1;
} catch (\Error $e) {
printf("%s: %s\n", $e::class, $e->getMessage());
}
// source type was not duplicated
unset($obj->b);
$refB = 1;
}
$reflector = new ReflectionClass(C::class);
$obj = $reflector->newLazyGhost(function ($obj) {
var_dump("initializer");
$obj->__construct();
});
test('Ghost', $obj);
$obj = $reflector->newLazyProxy(function ($obj) {
var_dump("initializer");
return new C(null);
});
test('Proxy', $obj);
--EXPECTF--
# Ghost:
lazy ghost object(C)#%d (2) {
["a"]=>
&NULL
["b"]=>
&NULL
}
string(11) "initializer"
Exception: initializer exception
lazy ghost object(C)#%d (2) {
["a"]=>
&NULL
["b"]=>
&NULL
}
TypeError: Cannot assign int to reference held by property C::$a of type ?C
TypeError: Cannot assign int to reference held by property C::$b of type ?C
# Proxy:
lazy proxy object(C)#%d (2) {
["a"]=>
&NULL
["b"]=>
&NULL
}
string(11) "initializer"
Exception: initializer exception
lazy proxy object(C)#%d (2) {
["a"]=>
&NULL
["b"]=>
&NULL
}
TypeError: Cannot assign int to reference held by property C::$a of type ?C
TypeError: Cannot assign int to reference held by property C::$b of type ?C

View File

@ -0,0 +1,46 @@
--TEST--
Lazy objects: properties with no default values are left uninitialized
--FILE--
<?php
class C {
public $a;
public int $b;
public function __construct() {
var_dump(__METHOD__);
}
}
$reflector = new ReflectionClass(C::class);
$obj = $reflector->newLazyGhost(function ($obj) {
var_dump("initializer");
$obj->__construct();
});
var_dump($obj);
var_dump($obj->a);
try {
var_dump($obj->b);
} catch (Error $e) {
printf("%s\n", $e);
}
var_dump($obj);
--EXPECTF--
lazy ghost object(C)#%d (0) {
["b"]=>
uninitialized(int)
}
string(11) "initializer"
string(14) "C::__construct"
NULL
Error: Typed property C::$b must not be accessed before initialization in %s:%d
Stack trace:
#0 {main}
object(C)#%d (1) {
["a"]=>
NULL
["b"]=>
uninitialized(int)
}

View File

@ -0,0 +1,29 @@
--TEST--
Lazy objects: initialization of proxy does not change object id
--FILE--
<?php
class MyObject {
public $a;
}
$reflector = new ReflectionClass(MyObject::class);
$object = new MyObject();
$objectId = spl_object_id($object);
$reflector->resetAsLazyProxy($object, function (MyObject $object) use (&$object2Id) {
$object2 = new MyObject();
$object2Id = spl_object_id($object2);
return $object2;
});
var_dump(spl_object_id($object) === $objectId);
$reflector->initializeLazyObject($object);
var_dump(spl_object_id($object) === $objectId);
var_dump(spl_object_id($object) !== $object2Id);
?>
--EXPECT--
bool(true)
bool(true)
bool(true)

View File

@ -0,0 +1,33 @@
--TEST--
Lazy objects: initialization of proxy does not change the class of the object
--FILE--
<?php
class A {
public string $s;
}
class B extends A {
public function foo() {
var_dump(__METHOD__);
}
}
$reflector = new ReflectionClass(B::class);
$o = $reflector->newLazyProxy(function (B $o) {
return new A();
});
var_dump(get_class($o));
$o->foo();
$o->s = 'init';
var_dump(get_class($o));
$o->foo();
?>
--EXPECT--
string(1) "B"
string(6) "B::foo"
string(1) "B"
string(6) "B::foo"

View File

@ -0,0 +1,47 @@
--TEST--
Lazy objects: props are initialized to default values before calling initializer
--FILE--
<?php
class C {
public $a = 1;
public int $b = 2;
public function __construct() {
var_dump(__METHOD__);
$this->a = 3;
$this->b = 4;
}
}
$reflector = new ReflectionClass(C::class);
$obj = $reflector->newLazyGhost(function ($obj) {
var_dump("initializer");
var_dump($obj);
$obj->__construct();
});
var_dump($obj);
var_dump($obj->a);
var_dump($obj);
--EXPECTF--
lazy ghost object(C)#%d (0) {
["b"]=>
uninitialized(int)
}
string(11) "initializer"
object(C)#%d (2) {
["a"]=>
int(1)
["b"]=>
int(2)
}
string(14) "C::__construct"
int(3)
object(C)#%d (2) {
["a"]=>
int(3)
["b"]=>
int(4)
}

View File

@ -0,0 +1,58 @@
--TEST--
Lazy objects: array cast does not initialize object
--FILE--
<?php
class C {
public int $a;
public function __construct() {
var_dump(__METHOD__);
$this->a = 1;
}
}
$reflector = new ReflectionClass(C::class);
print "# Ghost:\n";
$obj = $reflector->newLazyGhost(function ($obj) {
var_dump("initializer");
$obj->__construct();
});
var_dump((array)$obj);
$obj->a = 2;
var_dump((array)$obj);
print "# Proxy:\n";
$obj = $reflector->newLazyProxy(function ($obj) {
var_dump("initializer");
return new C();
});
var_dump((array)$obj);
$obj->a = 2;
var_dump((array)$obj);
--EXPECTF--
# Ghost:
array(0) {
}
string(11) "initializer"
string(14) "C::__construct"
array(1) {
["a"]=>
int(2)
}
# Proxy:
array(0) {
}
string(11) "initializer"
string(14) "C::__construct"
array(1) {
["a"]=>
int(2)
}

View File

@ -0,0 +1,54 @@
--TEST--
Lazy objects: comparison initializes object
--FILE--
<?php
class C {
public int $a;
public $b;
public function __construct() {
var_dump(__METHOD__);
}
public function __toString() {
return 'C';
}
}
$reflector = new ReflectionClass(C::class);
$a = $reflector->newLazyGhost(function ($obj) {
$obj->__construct();
});
$b = $reflector->newLazyProxy(function ($obj) {
return new C();
});
var_dump($a > $b);
$a = $reflector->newLazyGhost(function ($obj) {
$obj->__construct();
});
$b = $reflector->newLazyProxy(function ($obj) {
return new C();
});
var_dump($a == $b);
$a = $reflector->newLazyGhost(function ($obj) {
$obj->__construct();
});
var_dump('A' < $a);
?>
--EXPECT--
string(14) "C::__construct"
string(14) "C::__construct"
bool(false)
string(14) "C::__construct"
string(14) "C::__construct"
bool(true)
bool(true)

View File

@ -0,0 +1,63 @@
--TEST--
Lazy objects: debug_zval_dump does not initialize object
--FILE--
<?php
class C {
public int $a;
public function __construct() {
var_dump(__METHOD__);
$this->a = 1;
}
}
$reflector = new ReflectionClass(C::class);
print "# Ghost:\n";
$obj = $reflector->newLazyGhost(function ($obj) {
var_dump("initializer");
$obj->__construct();
});
debug_zval_dump($obj);
$reflector->initializeLazyObject($obj);
debug_zval_dump($obj);
print "# Proxy:\n";
$obj = $reflector->newLazyProxy(function ($obj) {
var_dump("initializer");
return new C();
});
debug_zval_dump($obj);
$reflector->initializeLazyObject($obj);
debug_zval_dump($obj);
--EXPECTF--
# Ghost:
lazy ghost object(C)#%d (0) refcount(2){
["a"]=>
uninitialized(int)
}
string(11) "initializer"
string(14) "C::__construct"
object(C)#%d (1) refcount(2){
["a"]=>
int(1)
}
# Proxy:
lazy proxy object(C)#%d (0) refcount(2){
["a"]=>
uninitialized(int)
}
string(11) "initializer"
string(14) "C::__construct"
lazy proxy object(C)#%d (1) refcount(2){
["instance"]=>
object(C)#%d (1) refcount(2){
["a"]=>
int(1)
}
}

View File

@ -0,0 +1,48 @@
--TEST--
Lazy objects: Foreach initializes object
--FILE--
<?php
class C {
public int $a;
public function __construct() {
var_dump(__METHOD__);
$this->a = 1;
}
}
$reflector = new ReflectionClass(C::class);
print "# Ghost:\n";
$obj = $reflector->newLazyGhost(function ($obj) {
var_dump("initializer");
$obj->__construct();
});
foreach ($obj as $prop => $value) {
var_dump($prop, $value);
}
print "# Proxy:\n";
$obj = $reflector->newLazyProxy(function ($obj) {
var_dump("initializer");
return new C();
});
foreach ($obj as $prop => $value) {
var_dump($prop, $value);
}
--EXPECTF--
# Ghost:
string(11) "initializer"
string(14) "C::__construct"
string(1) "a"
int(1)
# Proxy:
string(11) "initializer"
string(14) "C::__construct"
string(1) "a"
int(1)

View File

@ -0,0 +1,96 @@
--TEST--
Lazy objects: Foreach initializes object
--FILE--
<?php
#[AllowDynamicProperties]
class C {
public int $a;
public int $b {
get { return $this->b; }
set(int $value) { $this->b = $value; }
}
public int $c {
get { return $this->a + 2; }
}
public function __construct() {
var_dump(__METHOD__);
$this->a = 1;
$this->b = 2;
$this->d = 4;
}
}
$reflector = new ReflectionClass(C::class);
print "# Ghost:\n";
$obj = $reflector->newLazyGhost(function ($obj) {
var_dump("initializer");
$obj->__construct();
});
foreach ($obj as $prop => $value) {
var_dump($prop, $value);
}
print "# Proxy:\n";
$obj = $reflector->newLazyProxy(function ($obj) {
var_dump("initializer");
return new C();
});
foreach ($obj as $prop => $value) {
var_dump($prop, $value);
}
print "# Ghost (init exception):\n";
$obj = $reflector->newLazyGhost(function ($obj) {
throw new \Exception();
});
try {
var_dump(json_encode($obj));
} catch (\Exception $e) {
printf("%s: %s\n", $e::class, $e->getMessage());
}
print "# Proxy (init exception):\n";
$obj = $reflector->newLazyProxy(function ($obj) {
throw new \Exception();
});
try {
var_dump(json_encode($obj));
} catch (\Exception $e) {
printf("%s: %s\n", $e::class, $e->getMessage());
}
--EXPECT--
# Ghost:
string(11) "initializer"
string(14) "C::__construct"
string(1) "a"
int(1)
string(1) "b"
int(2)
string(1) "c"
int(3)
string(1) "d"
int(4)
# Proxy:
string(11) "initializer"
string(14) "C::__construct"
string(1) "a"
int(1)
string(1) "b"
int(2)
string(1) "c"
int(3)
# Ghost (init exception):
Exception:
# Proxy (init exception):
Exception:

View File

@ -0,0 +1,56 @@
--TEST--
Lazy objects: get_mangled_object_vars does not initialize object
--FILE--
<?php
class C {
public int $a;
public function __construct() {
var_dump(__METHOD__);
$this->a = 1;
}
}
function test(string $name, object $obj) {
printf("# %s:\n", $name);
var_dump(get_mangled_object_vars($obj));
$obj->a = 2;
var_dump(get_mangled_object_vars($obj));
}
$reflector = new ReflectionClass(C::class);
$obj = $reflector->newLazyGhost(function ($obj) {
var_dump("initializer");
$obj->__construct();
});
test('Ghost', $obj);
$obj = $reflector->newLazyProxy(function ($obj) {
var_dump("initializer");
return new C();
});
test('Proxy', $obj);
--EXPECTF--
# Ghost:
array(0) {
}
string(11) "initializer"
string(14) "C::__construct"
array(1) {
["a"]=>
int(2)
}
# Proxy:
array(0) {
}
string(11) "initializer"
string(14) "C::__construct"
array(1) {
["a"]=>
int(2)
}

View File

@ -0,0 +1,62 @@
--TEST--
Lazy objects: get_object_vars initializes object
--FILE--
<?php
class C {
public int $a;
public function __construct() {
var_dump(__METHOD__);
$this->a = 1;
}
}
$reflector = new ReflectionClass(C::class);
print "# Ghost:\n";
$obj = $reflector->newLazyGhost(function ($obj) {
var_dump("initializer");
$obj->__construct();
});
var_dump(get_object_vars($obj));
$obj->a = 2;
var_dump(get_object_vars($obj));
print "# Proxy:\n";
$obj = $reflector->newLazyProxy(function ($obj) {
var_dump("initializer");
return new C();
});
var_dump(get_object_vars($obj));
$obj->a = 2;
var_dump(get_object_vars($obj));
--EXPECT--
# Ghost:
string(11) "initializer"
string(14) "C::__construct"
array(1) {
["a"]=>
int(1)
}
array(1) {
["a"]=>
int(2)
}
# Proxy:
string(11) "initializer"
string(14) "C::__construct"
array(1) {
["a"]=>
int(1)
}
array(1) {
["a"]=>
int(2)
}

View File

@ -0,0 +1,42 @@
--TEST--
Lazy objects: json_encode initializes object
--FILE--
<?php
class C {
public int $a;
public function __construct() {
var_dump(__METHOD__);
$this->a = 1;
}
}
$reflector = new ReflectionClass(C::class);
print "# Ghost:\n";
$obj = $reflector->newLazyGhost(function ($obj) {
var_dump("initializer");
$obj->__construct();
});
var_dump(json_encode($obj));
print "# Proxy:\n";
$obj = $reflector->newLazyProxy(function ($obj) {
var_dump("initializer");
return new C();
});
var_dump(json_encode($obj));
--EXPECTF--
# Ghost:
string(11) "initializer"
string(14) "C::__construct"
string(7) "{"a":1}"
# Proxy:
string(11) "initializer"
string(14) "C::__construct"
string(7) "{"a":1}"

View File

@ -0,0 +1,80 @@
--TEST--
Lazy objects: json_encode initializes object
--FILE--
<?php
#[AllowDynamicProperties]
class C {
public int $a;
public int $b {
get { return $this->b; }
set(int $value) { $this->b = $value; }
}
public int $c {
get { return $this->a + 2; }
}
public function __construct() {
var_dump(__METHOD__);
$this->a = 1;
$this->b = 2;
$this->d = 4;
}
}
$reflector = new ReflectionClass(C::class);
print "# Ghost:\n";
$obj = $reflector->newLazyGhost(function ($obj) {
var_dump("initializer");
$obj->__construct();
});
var_dump(json_encode($obj));
print "# Proxy:\n";
$obj = $reflector->newLazyProxy(function ($obj) {
var_dump("initializer");
return new C();
});
var_dump(json_encode($obj));
print "# Ghost (init exception):\n";
$obj = $reflector->newLazyGhost(function ($obj) {
throw new \Exception();
});
try {
var_dump(json_encode($obj));
} catch (\Exception $e) {
printf("%s: %s\n", $e::class, $e->getMessage());
}
print "# Proxy (init exception):\n";
$obj = $reflector->newLazyProxy(function ($obj) {
throw new \Exception();
});
try {
var_dump(json_encode($obj));
} catch (\Exception $e) {
printf("%s: %s\n", $e::class, $e->getMessage());
}
--EXPECT--
# Ghost:
string(11) "initializer"
string(14) "C::__construct"
string(25) "{"a":1,"b":2,"c":3,"d":4}"
# Proxy:
string(11) "initializer"
string(14) "C::__construct"
string(25) "{"a":1,"b":2,"c":3,"d":4}"
# Ghost (init exception):
Exception:
# Proxy (init exception):
Exception:

View File

@ -0,0 +1,45 @@
--TEST--
Lazy objects: ReflectionObject::__toString() does not trigger initialization
--FILE--
<?php
class C {
public int $a;
public function __construct() {
var_dump(__METHOD__);
$this->a = 1;
}
}
function test(string $name, object $obj) {
printf("# %s\n", $name);
(new ReflectionObject($obj))->__toString();
printf("Initialized:\n");
var_dump(!(new ReflectionClass($obj))->isUninitializedLazyObject($obj));
}
$reflector = new ReflectionClass(C::class);
$obj = $reflector->newLazyGhost(function ($obj) {
var_dump("initializer");
$obj->__construct();
});
test('Ghost', $obj);
$obj = $reflector->newLazyProxy(function ($obj) {
var_dump("initializer");
return new C();
});
test('Proxy', $obj);
--EXPECT--
# Ghost
Initialized:
bool(false)
# Proxy
Initialized:
bool(false)

View File

@ -0,0 +1,43 @@
--TEST--
Lazy objects: serialize initializes object
--FILE--
<?php
class C {
public int $a;
public function __construct() {
var_dump(__METHOD__);
$this->a = 1;
}
}
$reflector = new ReflectionClass(C::class);
print "# Ghost:\n";
$obj = $reflector->newLazyGhost(function ($obj) {
var_dump("initializer");
$obj->__construct();
});
var_dump(serialize($obj));
print "# Proxy:\n";
$obj = $reflector->newLazyProxy(function ($obj) {
var_dump("initializer");
return new C();
});
var_dump(serialize($obj));
--EXPECTF--
# Ghost:
string(11) "initializer"
string(14) "C::__construct"
string(24) "O:1:"C":1:{s:1:"a";i:1;}"
# Proxy:
string(11) "initializer"
string(14) "C::__construct"
string(24) "O:1:"C":1:{s:1:"a";i:1;}"

View File

@ -0,0 +1,63 @@
--TEST--
Lazy objects: var_dump does not initialize object
--FILE--
<?php
class C {
public int $a;
public function __construct() {
var_dump(__METHOD__);
$this->a = 1;
}
}
$reflector = new ReflectionClass(C::class);
print "# Ghost:\n";
$obj = $reflector->newLazyGhost(function ($obj) {
var_dump("initializer");
$obj->__construct();
});
var_dump($obj);
$reflector->initializeLazyObject($obj);
var_dump($obj);
print "# Proxy:\n";
$obj = $reflector->newLazyProxy(function ($obj) {
var_dump("initializer");
return new C();
});
var_dump($obj);
$reflector->initializeLazyObject($obj);
var_dump($obj);
--EXPECTF--
# Ghost:
lazy ghost object(C)#%d (0) {
["a"]=>
uninitialized(int)
}
string(11) "initializer"
string(14) "C::__construct"
object(C)#%d (1) {
["a"]=>
int(1)
}
# Proxy:
lazy proxy object(C)#%d (0) {
["a"]=>
uninitialized(int)
}
string(11) "initializer"
string(14) "C::__construct"
lazy proxy object(C)#%d (1) {
["instance"]=>
object(C)#%d (1) {
["a"]=>
int(1)
}
}

View File

@ -0,0 +1,57 @@
--TEST--
Lazy objects: var_dump may not initialize object with __debugInfo() method
--FILE--
<?php
class C {
public int $a;
public function __construct() {
var_dump(__METHOD__);
$this->a = 1;
}
public function __debugInfo() {
return ['hello'];
}
}
function test(string $name, object $obj) {
$reflector = new ReflectionClass(C::class);
printf("# %s\n", $name);
var_dump($obj);
printf("Initialized:\n");
var_dump(!$reflector->isUninitializedLazyObject($obj));
}
$reflector = new ReflectionClass(C::class);
$obj = $reflector->newLazyGhost(function ($obj) {
var_dump("initializer");
$obj->__construct();
});
test('Ghost', $obj);
$obj = $reflector->newLazyProxy(function ($obj) {
var_dump("initializer");
return new C();
});
test('Proxy', $obj);
--EXPECTF--
# Ghost
lazy ghost object(C)#%d (1) {
[0]=>
string(5) "hello"
}
Initialized:
bool(false)
# Proxy
lazy proxy object(C)#%d (1) {
[0]=>
string(5) "hello"
}
Initialized:
bool(false)

View File

@ -0,0 +1,60 @@
--TEST--
Lazy objects: var_dump may initialize object with __debugInfo() method
--FILE--
<?php
class C {
public int $a;
public function __construct() {
var_dump(__METHOD__);
$this->a = 1;
}
public function __debugInfo() {
return [$this->a];
}
}
function test(string $name, object $obj) {
$reflector = new ReflectionClass(C::class);
printf("# %s\n", $name);
var_dump($obj);
printf("Initialized:\n");
var_dump(!$reflector->isUninitializedLazyObject($obj));
}
$reflector = new ReflectionClass(C::class);
$obj = $reflector->newLazyGhost(function ($obj) {
var_dump("initializer");
$obj->__construct();
});
test('Ghost', $obj);
$obj = $reflector->newLazyProxy(function ($obj) {
var_dump("initializer");
return new C();
});
test('Proxy', $obj);
--EXPECTF--
# Ghost
string(11) "initializer"
string(14) "C::__construct"
object(C)#%d (1) {
[0]=>
int(1)
}
Initialized:
bool(true)
# Proxy
string(11) "initializer"
string(14) "C::__construct"
lazy proxy object(C)#%d (1) {
[0]=>
int(1)
}
Initialized:
bool(true)

View File

@ -0,0 +1,47 @@
--TEST--
Lazy objects: var_export initializes object
--FILE--
<?php
class C {
public int $a;
public function __construct() {
var_dump(__METHOD__);
$this->a = 1;
}
}
$reflector = new ReflectionClass(C::class);
print "# Ghost:\n";
$obj = $reflector->newLazyGhost(function ($obj) {
var_dump("initializer");
$obj->__construct();
});
var_export($obj);
print "\n";
print "# Proxy:\n";
$obj = $reflector->newLazyProxy(function ($obj) {
var_dump("initializer");
return new C();
});
var_export($obj);
print "\n";
--EXPECTF--
# Ghost:
string(11) "initializer"
string(14) "C::__construct"
\C::__set_state(array(
'a' => 1,
))
# Proxy:
string(11) "initializer"
string(14) "C::__construct"
\C::__set_state(array(
'a' => 1,
))

View File

@ -0,0 +1,62 @@
--TEST--
Lazy objects: ReflectionClass::initializeLazyObject()
--FILE--
<?php
class C {
public int $a;
}
function test(string $name, object $obj) {
printf("# %s:\n", $name);
$reflector = new ReflectionClass(C::class);
printf("Initialized:\n");
var_dump(!$reflector->isUninitializedLazyObject($obj));
var_dump($reflector?->initializeLazyObject($obj));
printf("Initialized:\n");
var_dump(!$reflector->isUninitializedLazyObject($obj));
}
$reflector = new ReflectionClass(C::class);
$obj = $reflector->newLazyGhost(function ($obj) {
var_dump("initializer");
$obj->a = 1;
});
test('Ghost', $obj);
$obj = $reflector->newLazyProxy(function ($obj) {
var_dump("initializer");
$c = new C();
$c->a = 1;
return $c;
});
test('Proxy', $obj);
--EXPECTF--
# Ghost:
Initialized:
bool(false)
string(11) "initializer"
object(C)#%d (1) {
["a"]=>
int(1)
}
Initialized:
bool(true)
# Proxy:
Initialized:
bool(false)
string(11) "initializer"
object(C)#%d (1) {
["a"]=>
int(1)
}
Initialized:
bool(true)

View File

@ -0,0 +1,51 @@
--TEST--
Lazy objects: ReflectionClass::initializeLazyObject() error
--FILE--
<?php
class C {
public int $a;
}
function test(string $name, object $obj) {
printf("# %s:\n", $name);
$reflector = new ReflectionClass(C::class);
var_dump(!$reflector->isUninitializedLazyObject($obj));
try {
var_dump($reflector?->initializeLazyObject($obj));
} catch (Exception $e) {
printf("%s\n", $e->getMessage());
}
var_dump(!$reflector->isUninitializedLazyObject($obj));
}
$reflector = new ReflectionClass(C::class);
$obj = $reflector->newLazyGhost(function ($obj) {
var_dump("initializer");
throw new \Exception('initializer exception');
});
test('Ghost', $obj);
$obj = $reflector->newLazyProxy(function ($obj) {
var_dump("initializer");
throw new \Exception('initializer exception');
});
test('Proxy', $obj);
--EXPECT--
# Ghost:
bool(false)
string(11) "initializer"
initializer exception
bool(false)
# Proxy:
bool(false)
string(11) "initializer"
initializer exception
bool(false)

View File

@ -0,0 +1,60 @@
--TEST--
Lazy objects: ReflectionClass::initializeLazyObject() on an initialized object is a no-op
--FILE--
<?php
class C {
public int $a;
}
function test(string $name, object $obj) {
printf("# %s:\n", $name);
$reflector = new ReflectionClass(C::class);
var_dump($obj->a);
var_dump(!$reflector->isUninitializedLazyObject($obj));
var_dump($reflector?->initializeLazyObject($obj));
var_dump(!$reflector->isUninitializedLazyObject($obj));
}
$reflector = new ReflectionClass(C::class);
$obj = $reflector->newLazyGhost(function ($obj) {
var_dump("initializer");
$obj->a = 1;
});
test('Ghost', $obj);
$obj = $reflector->newLazyProxy(function ($obj) {
var_dump("initializer");
$c = new C();
$c->a = 1;
return $c;
});
test('Proxy', $obj);
--EXPECTF--
# Ghost:
string(11) "initializer"
int(1)
bool(true)
object(C)#%d (1) {
["a"]=>
int(1)
}
bool(true)
# Proxy:
string(11) "initializer"
int(1)
bool(true)
object(C)#%d (1) {
["a"]=>
int(1)
}
bool(true)

View File

@ -0,0 +1,164 @@
--TEST--
Lazy objects: initializer must return the right type
--FILE--
<?php
class B {
public int $b;
public function __construct() {
$this->b = 1;
}
public function __destruct() {
}
}
class C extends B {
}
class D extends C {
public int $b; // override
}
class E extends B {
public function __destruct() { // override
}
}
$reflector = new ReflectionClass(C::class);
print "# Ghost initializer must return NULL or no value:\n";
$obj = $reflector->newLazyGhost(function ($obj) {
var_dump("initializer");
$obj->__construct();
return new stdClass;
});
var_dump($obj);
try {
var_dump($obj->a);
} catch (\Error $e) {
printf("%s: %s\n", $e::class, $e->getMessage());
}
var_dump($obj);
print "# Proxy initializer must return an instance of a compatible class:\n";
print "## Valid cases:\n";
$tests = [
[C::class, new C()],
[C::class, new B()],
[D::class, new B()],
];
foreach ($tests as [$class, $instance]) {
$obj = (new ReflectionClass($class))->newLazyProxy(function ($obj) use ($instance) {
var_dump("initializer");
$instance->b = 1;
return $instance;
});
printf("## %s vs %s\n", get_class($obj), is_object($instance) ? get_class($instance) : gettype($instance));
var_dump($obj->b);
var_dump($obj);
}
print "## Invalid cases:\n";
$tests = [
[C::class, new stdClass],
[C::class, new DateTime()],
[C::class, null],
[C::class, new D()],
[E::class, new B()],
];
foreach ($tests as [$class, $instance]) {
$obj = (new ReflectionClass($class))->newLazyProxy(function ($obj) use ($instance) {
var_dump("initializer");
return $instance;
});
try {
printf("## %s vs %s\n", get_class($obj), is_object($instance) ? get_class($instance) : gettype($instance));
var_dump($obj->a);
} catch (\Error $e) {
printf("%s: %s\n", $e::class, $e->getMessage());
}
}
$obj = $reflector->newLazyProxy(function ($obj) {
var_dump("initializer");
return $obj;
});
try {
printf("## %s vs itself\n", get_class($obj));
var_dump($obj->a);
} catch (\Error $e) {
printf("%s: %s\n", $e::class, $e->getMessage());
}
--EXPECTF--
# Ghost initializer must return NULL or no value:
lazy ghost object(C)#%d (0) {
["b"]=>
uninitialized(int)
}
string(11) "initializer"
TypeError: Lazy object initializer must return NULL or no value
lazy ghost object(C)#%d (0) {
["b"]=>
uninitialized(int)
}
# Proxy initializer must return an instance of a compatible class:
## Valid cases:
## C vs C
string(11) "initializer"
int(1)
lazy proxy object(C)#%d (1) {
["instance"]=>
object(C)#%d (1) {
["b"]=>
int(1)
}
}
## C vs B
string(11) "initializer"
int(1)
lazy proxy object(C)#%d (1) {
["instance"]=>
object(B)#%d (1) {
["b"]=>
int(1)
}
}
## D vs B
string(11) "initializer"
int(1)
lazy proxy object(D)#%d (1) {
["instance"]=>
object(B)#%d (1) {
["b"]=>
int(1)
}
}
## Invalid cases:
## C vs stdClass
string(11) "initializer"
TypeError: The real instance class stdClass is not compatible with the proxy class C. The proxy must be a instance of the same class as the real instance, or a sub-class with no additional properties, and no overrides of the __destructor or __clone methods.
## C vs DateTime
string(11) "initializer"
TypeError: The real instance class DateTime is not compatible with the proxy class C. The proxy must be a instance of the same class as the real instance, or a sub-class with no additional properties, and no overrides of the __destructor or __clone methods.
## C vs NULL
string(11) "initializer"
TypeError: Lazy proxy factory must return an instance of a class compatible with C, null returned
## C vs D
string(11) "initializer"
TypeError: The real instance class D is not compatible with the proxy class C. The proxy must be a instance of the same class as the real instance, or a sub-class with no additional properties, and no overrides of the __destructor or __clone methods.
## E vs B
string(11) "initializer"
TypeError: The real instance class B is not compatible with the proxy class E. The proxy must be a instance of the same class as the real instance, or a sub-class with no additional properties, and no overrides of the __destructor or __clone methods.
## C vs itself
string(11) "initializer"
Error: Lazy proxy factory must return a non-lazy object

View File

@ -0,0 +1,51 @@
--TEST--
Lazy objects: invalid options
--FILE--
<?php
class C {
public $a = 1;
}
$reflector = new ReflectionClass(C::class);
try {
$obj = $reflector->newLazyGhost(function ($obj) { }, -1);
} catch (ReflectionException $e) {
printf("%s: %s\n", $e::class, $e->getMessage());
}
try {
$obj = $reflector->newLazyProxy(function ($obj) { }, -1);
} catch (ReflectionException $e) {
printf("%s: %s\n", $e::class, $e->getMessage());
}
try {
// SKIP_DESTRUCTOR is only allowed on resetAsLazyProxy()
$obj = $reflector->newLazyGhost(function ($obj) { }, ReflectionClass::SKIP_DESTRUCTOR);
} catch (ReflectionException $e) {
printf("%s: %s\n", $e::class, $e->getMessage());
}
$obj = new C();
try {
$reflector->resetAsLazyGhost($obj, function ($obj) { }, -1);
} catch (ReflectionException $e) {
printf("%s: %s\n", $e::class, $e->getMessage());
}
try {
$reflector->resetAsLazyProxy($obj, function ($obj) { }, -1);
} catch (ReflectionException $e) {
printf("%s: %s\n", $e::class, $e->getMessage());
}
?>
--EXPECT--
ReflectionException: ReflectionClass::newLazyGhost(): Argument #2 ($options) contains invalid flags
ReflectionException: ReflectionClass::newLazyProxy(): Argument #2 ($options) contains invalid flags
ReflectionException: ReflectionClass::newLazyGhost(): Argument #2 ($options) does not accept ReflectionClass::SKIP_DESTRUCTOR
ReflectionException: ReflectionClass::resetAsLazyGhost(): Argument #3 ($options) contains invalid flags
ReflectionException: ReflectionClass::resetAsLazyProxy(): Argument #3 ($options) contains invalid flags

View File

@ -0,0 +1,49 @@
--TEST--
Lazy objects: ReflectionClass::isUninitializedLazyObject()
--FILE--
<?php
class C {
public int $a;
}
function test(string $name, object $obj) {
$reflector = new ReflectionClass(C::class);
printf("# %s\n", $name);
var_dump($reflector->isUninitializedLazyObject($obj));
var_dump($obj->a);
var_dump($reflector->isUninitializedLazyObject($obj));
}
$reflector = new ReflectionClass(C::class);
$obj = $reflector->newLazyGhost(function ($obj) {
var_dump("initializer");
$obj->a = 1;
});
test('Ghost', $obj);
$obj = $reflector->newLazyProxy(function ($obj) {
var_dump("initializer");
$obj = new C();
$obj->a = 1;
return $obj;
});
test('Proxy', $obj);
?>
--EXPECT--
# Ghost
bool(true)
string(11) "initializer"
int(1)
bool(false)
# Proxy
bool(true)
string(11) "initializer"
int(1)
bool(false)

View File

@ -0,0 +1,75 @@
--TEST--
Lazy objects: hooked property isset initializes object if hook observes object state
--FILE--
<?php
class C {
public $a {
get { return $this->a; }
set($value) { $this->a = $value; }
}
public int $b = 1;
public function __construct(int $a) {
var_dump(__METHOD__);
$this->a = $a;
$this->b = 2;
}
}
function test(string $name, object $obj) {
printf("# %s:\n", $name);
var_dump($obj);
var_dump(isset($obj->a));
var_dump($obj);
}
$reflector = new ReflectionClass(C::class);
$obj = $reflector->newLazyGhost(function ($obj) {
var_dump("initializer");
$obj->__construct(1);
});
test('Ghost', $obj);
$obj = $reflector->newLazyProxy(function ($obj) {
var_dump("initializer");
return new C(1);
});
test('Proxy', $obj);
--EXPECTF--
# Ghost:
lazy ghost object(C)#%d (0) {
["b"]=>
uninitialized(int)
}
string(11) "initializer"
string(14) "C::__construct"
bool(true)
object(C)#%d (2) {
["a"]=>
int(1)
["b"]=>
int(2)
}
# Proxy:
lazy proxy object(C)#%d (0) {
["b"]=>
uninitialized(int)
}
string(11) "initializer"
string(14) "C::__construct"
bool(true)
lazy proxy object(C)#%d (1) {
["instance"]=>
object(C)#%d (2) {
["a"]=>
int(1)
["b"]=>
int(2)
}
}

View File

@ -0,0 +1,64 @@
--TEST--
Lazy objects: hooked property isset may does not initialize object if hook does not observe object state
--FILE--
<?php
class C {
public $a {
get { return 1; }
set($value) { }
}
public int $b = 1;
public function __construct(int $a) {
var_dump(__METHOD__);
$this->a = $a;
$this->b = 2;
}
}
function test(string $name, object $obj) {
printf("# %s:\n", $name);
var_dump($obj);
var_dump(isset($obj->a));
var_dump($obj);
}
$reflector = new ReflectionClass(C::class);
$obj = $reflector->newLazyGhost(function ($obj) {
var_dump("initializer");
$obj->__construct(1);
});
test('Ghost', $obj);
$obj = $reflector->newLazyProxy(function ($obj) {
var_dump("initializer");
return new C(1);
});
test('Proxy', $obj);
--EXPECTF--
# Ghost:
lazy ghost object(C)#%d (0) {
["b"]=>
uninitialized(int)
}
bool(true)
lazy ghost object(C)#%d (0) {
["b"]=>
uninitialized(int)
}
# Proxy:
lazy proxy object(C)#%d (0) {
["b"]=>
uninitialized(int)
}
bool(true)
lazy proxy object(C)#%d (0) {
["b"]=>
uninitialized(int)
}

View File

@ -0,0 +1,72 @@
--TEST--
Lazy objects: property isset initializes object
--FILE--
<?php
class C {
public $a;
public int $b = 1;
public function __construct(int $a) {
var_dump(__METHOD__);
$this->a = $a;
$this->b = 2;
}
}
function test(string $name, object $obj) {
printf("# %s:\n", $name);
var_dump($obj);
var_dump(isset($obj->a));
var_dump($obj);
}
$reflector = new ReflectionClass(C::class);
$obj = $reflector->newLazyGhost(function ($obj) {
var_dump("initializer");
$obj->__construct(1);
});
test('Ghost', $obj);
$obj = $reflector->newLazyProxy(function ($obj) {
var_dump("initializer");
return new C(1);
});
test('Proxy', $obj);
--EXPECTF--
# Ghost:
lazy ghost object(C)#%d (0) {
["b"]=>
uninitialized(int)
}
string(11) "initializer"
string(14) "C::__construct"
bool(true)
object(C)#%d (2) {
["a"]=>
int(1)
["b"]=>
int(2)
}
# Proxy:
lazy proxy object(C)#%d (0) {
["b"]=>
uninitialized(int)
}
string(11) "initializer"
string(14) "C::__construct"
bool(true)
lazy proxy object(C)#%d (1) {
["instance"]=>
object(C)#%d (2) {
["a"]=>
int(1)
["b"]=>
int(2)
}
}

View File

@ -0,0 +1,69 @@
--TEST--
Lazy objects: JIT: ASSIGN_OBJ with dynamic prop
--FILE--
<?php
#[AllowDynamicProperties]
class C {
public int $b = 1;
public function __construct() {
var_dump(__METHOD__);
$this->b = 3;
}
}
function test(string $name, object $obj) {
printf("# %s:\n", $name);
var_dump($obj);
$obj->a = 2;
var_dump($obj);
}
$reflector = new ReflectionClass(C::class);
$obj = $reflector->newLazyGhost(function ($obj) {
var_dump("initializer");
$obj->__construct();
});
test('Ghost', $obj);
$obj = $reflector->newLazyProxy(function ($obj) {
var_dump("initializer");
return new C();
});
test('Proxy', $obj);
--EXPECTF--
# Ghost:
lazy ghost object(C)#%d (0) {
["b"]=>
uninitialized(int)
}
string(11) "initializer"
string(14) "C::__construct"
object(C)#%d (2) {
["b"]=>
int(3)
["a"]=>
int(2)
}
# Proxy:
lazy proxy object(C)#%d (0) {
["b"]=>
uninitialized(int)
}
string(11) "initializer"
string(14) "C::__construct"
lazy proxy object(C)#%d (1) {
["instance"]=>
object(C)#%d (2) {
["b"]=>
int(3)
["a"]=>
int(2)
}
}

View File

@ -0,0 +1,70 @@
--TEST--
Lazy objects: JIT: ASSIGN_OBJ_OP with dynamic prop
--FILE--
<?php
#[AllowDynamicProperties]
class C {
public int $b = 1;
public function __construct() {
var_dump(__METHOD__);
$this->a = 1;
$this->b = 3;
}
}
function test(string $name, object $obj) {
printf("# %s:\n", $name);
var_dump($obj);
$obj->a += 1;
var_dump($obj);
}
$reflector = new ReflectionClass(C::class);
$obj = $reflector->newLazyGhost(function ($obj) {
var_dump("initializer");
$obj->__construct();
});
test('Ghost', $obj);
$obj = $reflector->newLazyProxy(function ($obj) {
var_dump("initializer");
return new C();
});
test('Proxy', $obj);
--EXPECTF--
# Ghost:
lazy ghost object(C)#%d (0) {
["b"]=>
uninitialized(int)
}
string(11) "initializer"
string(14) "C::__construct"
object(C)#%d (2) {
["b"]=>
int(1)
["a"]=>
int(2)
}
# Proxy:
lazy proxy object(C)#%d (0) {
["b"]=>
uninitialized(int)
}
string(11) "initializer"
string(14) "C::__construct"
lazy proxy object(C)#%d (1) {
["instance"]=>
object(C)#%d (2) {
["b"]=>
int(3)
["a"]=>
int(2)
}
}

View File

@ -0,0 +1,69 @@
--TEST--
Lazy objects: JIT: ASSIGN_OBJ_OP with known prop_info
--FILE--
<?php
class C {
public $a;
public int $b = 1;
public function __construct() {
var_dump(__METHOD__);
$this->b = 3;
}
}
function test(string $name, object $obj) {
printf("# %s:\n", $name);
var_dump($obj);
$obj->a += 1;
var_dump($obj);
}
$reflector = new ReflectionClass(C::class);
$obj = $reflector->newLazyGhost(function ($obj) {
var_dump("initializer");
$obj->__construct();
});
test('Ghost', $obj);
$obj = $reflector->newLazyProxy(function ($obj) {
var_dump("initializer");
return new C();
});
test('Proxy', $obj);
--EXPECTF--
# Ghost:
lazy ghost object(C)#%d (0) {
["b"]=>
uninitialized(int)
}
string(11) "initializer"
string(14) "C::__construct"
object(C)#%d (2) {
["a"]=>
int(1)
["b"]=>
int(3)
}
# Proxy:
lazy proxy object(C)#%d (0) {
["b"]=>
uninitialized(int)
}
string(11) "initializer"
string(14) "C::__construct"
lazy proxy object(C)#%d (1) {
["instance"]=>
object(C)#%d (2) {
["a"]=>
int(1)
["b"]=>
int(3)
}
}

View File

@ -0,0 +1,47 @@
--TEST--
Lazy objects: JIT: ASSIGN_OBJ_OP with unknown prop info
--FILE--
<?php
class C {
// Private prop so that prop_info is not inferred
private int $a;
public int $b;
function __construct() {
$this->a = 1;
$this->b = 2;
}
function test(object $obj) {
$obj->a += 1;
}
}
$reflector = new ReflectionClass(C::class);
for ($i = 0; $i < 2; $i++) {
$obj = $reflector->newLazyGhost(function ($obj) {
var_dump("initializer");
$obj->__construct();
});
// Call via reflection to avoid inlining.
// - test() handlers are executed once, and prime the runtime cache
// - On subsequent calls, the JIT'ed code is used, and we enter the valid runtime cache path
$reflector->getMethod('test')->invoke($obj, $obj);
var_dump($obj);
}
--EXPECTF--
string(11) "initializer"
object(C)#%d (2) {
["a":"C":private]=>
int(2)
["b"]=>
int(2)
}
string(11) "initializer"
object(C)#%d (2) {
["a":"C":private]=>
int(2)
["b"]=>
int(2)
}

View File

@ -0,0 +1,48 @@
--TEST--
Lazy objects: JIT: ASSIGN_OBJ_OP with unknown prop info untyped
--FILE--
<?php
class C {
// Private prop so that prop_info is not inferred
private int $a;
public int $b;
function __construct() {
$this->a = 1;
$this->b = 2;
}
function test(object $obj) {
$obj->a += 1;
}
}
$reflector = new ReflectionClass(C::class);
for ($i = 0; $i < 2; $i++) {
$obj = $reflector->newLazyGhost(function ($obj) {
var_dump("initializer");
$obj->__construct();
});
// Call via reflection to avoid inlining.
// - test() handlers are executed once, and prime the runtime cache
// - On subsequent calls, the JIT'ed code is used, and we enter the valid runtime cache path
$reflector->getMethod('test')->invoke($obj, $obj);
var_dump($obj);
}
?>
--EXPECTF--
string(11) "initializer"
object(C)#%d (2) {
["a":"C":private]=>
int(2)
["b"]=>
int(2)
}
string(11) "initializer"
object(C)#%d (2) {
["a":"C":private]=>
int(2)
["b"]=>
int(2)
}

View File

@ -0,0 +1,69 @@
--TEST--
Lazy objects: JIT: ASSIGN_OBJ with known prop_info
--FILE--
<?php
class C {
public $a;
public int $b = 1;
public function __construct() {
var_dump(__METHOD__);
$this->b = 3;
}
}
function test(string $name, object $obj) {
printf("# %s:\n", $name);
var_dump($obj);
$obj->a = 2;
var_dump($obj);
}
$reflector = new ReflectionClass(C::class);
$obj = $reflector->newLazyGhost(function ($obj) {
var_dump("initializer");
$obj->__construct();
});
test('Ghost', $obj);
$obj = $reflector->newLazyProxy(function ($obj) {
var_dump("initializer");
return new C();
});
test('Proxy', $obj);
--EXPECTF--
# Ghost:
lazy ghost object(C)#%d (0) {
["b"]=>
uninitialized(int)
}
string(11) "initializer"
string(14) "C::__construct"
object(C)#%d (2) {
["a"]=>
int(2)
["b"]=>
int(3)
}
# Proxy:
lazy proxy object(C)#%d (0) {
["b"]=>
uninitialized(int)
}
string(11) "initializer"
string(14) "C::__construct"
lazy proxy object(C)#%d (1) {
["instance"]=>
object(C)#%d (2) {
["a"]=>
int(2)
["b"]=>
int(3)
}
}

View File

@ -0,0 +1,46 @@
--TEST--
Lazy objects: JIT: ASSIGN_OBJ with unknown prop info
--FILE--
<?php
class C {
// Private prop so that prop_info is not inferred
private int $a;
public int $b;
function __construct() {
$this->b = 2;
}
function test(object $obj) {
$obj->a = 1;
}
}
$reflector = new ReflectionClass(C::class);
for ($i = 0; $i < 2; $i++) {
$obj = $reflector->newLazyGhost(function ($obj) {
var_dump("initializer");
$obj->__construct();
});
// Call via reflection to avoid inlining.
// - test() handlers are executed once, and prime the runtime cache
// - On subsequent calls, the JIT'ed code is used, and we enter the valid runtime cache path
$reflector->getMethod('test')->invoke($obj, $obj);
var_dump($obj);
}
--EXPECTF--
string(11) "initializer"
object(C)#%d (2) {
["a":"C":private]=>
int(1)
["b"]=>
int(2)
}
string(11) "initializer"
object(C)#%d (2) {
["a":"C":private]=>
int(1)
["b"]=>
int(2)
}

View File

@ -0,0 +1,47 @@
--TEST--
Lazy objects: JIT: ASSIGN_OBJ with unknown prop info untyped
--FILE--
<?php
class C {
// Private prop so that prop_info is not inferred
private int $a;
public int $b;
function __construct() {
$this->b = 2;
}
function test(object $obj) {
$obj->a = 1;
}
}
$reflector = new ReflectionClass(C::class);
for ($i = 0; $i < 2; $i++) {
$obj = $reflector->newLazyGhost(function ($obj) {
var_dump("initializer");
$obj->__construct();
});
// Call via reflection to avoid inlining.
// - test() handlers are executed once, and prime the runtime cache
// - On subsequent calls, the JIT'ed code is used, and we enter the valid runtime cache path
$reflector->getMethod('test')->invoke($obj, $obj);
var_dump($obj);
}
?>
--EXPECTF--
string(11) "initializer"
object(C)#%d (2) {
["a":"C":private]=>
int(1)
["b"]=>
int(2)
}
string(11) "initializer"
object(C)#%d (2) {
["a":"C":private]=>
int(1)
["b"]=>
int(2)
}

View File

@ -0,0 +1,49 @@
--TEST--
Lazy objects: json_encode with dynamic props on initialized object
--FILE--
<?php
#[AllowDynamicProperties]
class C {
public int $a = 1;
}
$reflector = new ReflectionClass(C::class);
$reflector = new ReflectionClass(C::class);
function test(string $name, object $obj) {
printf("# %s\n", $name);
var_dump(json_decode(json_encode($obj)));
}
$obj = $reflector->newLazyGhost(function ($obj) {
$obj->dyn = 1;
});
test('Ghost', $obj);
$obj = $reflector->newLazyProxy(function () {
$c = new C();
$c->dyn = 1;
return $c;
});
test('Proxy', $obj);
--EXPECTF--
# Ghost
object(stdClass)#%d (2) {
["a"]=>
int(1)
["dyn"]=>
int(1)
}
# Proxy
object(stdClass)#%d (2) {
["a"]=>
int(1)
["dyn"]=>
int(1)
}

View File

@ -0,0 +1,66 @@
--TEST--
Lazy objects: markLazyObjectAsInitialized() initializes properties to their default value and skips initializer
--FILE--
<?php
class C {
public int $a = 1;
}
function test(string $name, object $obj) {
printf("# %s:\n", $name);
$reflector = new ReflectionClass(C::class);
printf("Initialized:\n");
var_dump(!$reflector->isUninitializedLazyObject($obj));
printf("markLazyObjectAsInitialized(true) returns \$obj:\n");
var_dump($reflector?->markLazyObjectAsInitialized($obj) === $obj);
printf("Initialized:\n");
var_dump(!$reflector->isUninitializedLazyObject($obj));
var_dump($obj);
}
$reflector = new ReflectionClass(C::class);
$obj = $reflector->newLazyGhost(function ($obj) {
var_dump("initializer");
$obj->a = 1;
});
test('Ghost', $obj);
$obj = $reflector->newLazyProxy(function ($obj) {
var_dump("initializer");
$c = new C();
$c->a = 1;
return $c;
});
test('Proxy', $obj);
--EXPECTF--
# Ghost:
Initialized:
bool(false)
markLazyObjectAsInitialized(true) returns $obj:
bool(true)
Initialized:
bool(true)
object(C)#%d (1) {
["a"]=>
int(1)
}
# Proxy:
Initialized:
bool(false)
markLazyObjectAsInitialized(true) returns $obj:
bool(true)
Initialized:
bool(true)
object(C)#%d (1) {
["a"]=>
int(1)
}

View File

@ -0,0 +1,96 @@
--TEST--
Lazy objects: Object is not lazy anymore if all props have been assigned a value
--FILE--
<?php
#[AllowDynamicProperties]
class B {
private readonly string $b;
public function __construct() {
$this->b = 'b';
}
}
#[AllowDynamicProperties]
class C extends B {
public string $a;
public function __construct() {
parent::__construct();
$this->a = 'a';
}
}
function test(string $name, object $obj) {
$reflector = new ReflectionClass(C::class);
printf("# %s:\n", $name);
var_dump(!$reflector->isUninitializedLazyObject($obj));
$reflector->getProperty('a')->setRawValueWithoutLazyInitialization($obj, 'a1');
var_dump(!$reflector->isUninitializedLazyObject($obj));
// Should not count a second prop initialization
$reflector->getProperty('a')->setRawValueWithoutLazyInitialization($obj, 'a2');
var_dump(!$reflector->isUninitializedLazyObject($obj));
try {
// Should not count a prop initialization
$reflector->getProperty('a')->setRawValueWithoutLazyInitialization($obj, new stdClass);
} catch (Error $e) {
printf("%s: %s\n", $e::class, $e->getMessage());
}
// Should not count a prop initialization
//$reflector->getProperty('b')->setRawValueWithoutLazyInitialization($obj, 'dynamic B');
//var_dump(!$reflector->isUninitializedLazyObject($obj));
(new ReflectionProperty(B::class, 'b'))->setRawValueWithoutLazyInitialization($obj, 'b');
var_dump(!$reflector->isUninitializedLazyObject($obj));
var_dump($obj);
}
$reflector = new ReflectionClass(C::class);
$obj = $reflector->newLazyGhost(function ($obj) {
var_dump("initializer");
$obj->__construct();
});
test('Ghost', $obj);
$obj = $reflector->newLazyProxy(function ($obj) {
var_dump("initializer");
return new C();
});
test('Proxy', $obj);
--EXPECTF--
# Ghost:
bool(false)
bool(false)
bool(false)
TypeError: Cannot assign stdClass to property C::$a of type string
bool(true)
object(C)#%d (2) {
["b":"B":private]=>
string(1) "b"
["a"]=>
string(2) "a2"
}
# Proxy:
bool(false)
bool(false)
bool(false)
TypeError: Cannot assign stdClass to property C::$a of type string
bool(true)
object(C)#%d (2) {
["b":"B":private]=>
string(1) "b"
["a"]=>
string(2) "a2"
}

View File

@ -0,0 +1,78 @@
--TEST--
Lazy objects: Object with no props is never lazy
--FILE--
<?php
class C {}
#[AllowDynamicProperties]
class D {}
function test(string $name, object $obj, object $obj2, object $obj3) {
printf("# %s:\n", $name);
var_dump((new ReflectionClass($obj::class))->isUninitializedLazyObject($obj));
var_dump($obj);
var_dump((new ReflectionClass($obj2::class))->isUninitializedLazyObject($obj2));
var_dump($obj2);
var_dump((new ReflectionClass($obj3::class))->isUninitializedLazyObject($obj3));
var_dump($obj3);
}
$obj = new C();
(new ReflectionClass($obj))->resetAsLazyGhost($obj, function ($obj) {
var_dump("initializer");
});
$obj2 = new D();
$obj2->dynamic = 'value';
(new ReflectionClass($obj2))->resetAsLazyGhost($obj2, function ($obj2) {
var_dump("initializer");
});
$obj3 = (new ReflectionClass(C::class))->newLazyGhost(function () {
var_dump("initializer");
});
test('Ghost', $obj, $obj2, $obj3);
$obj = new C();
(new ReflectionClass($obj))->resetAsLazyProxy($obj, function ($obj) {
var_dump("initializer");
});
$obj2 = new D();
$obj2->dynamic = 'value';
(new ReflectionClass($obj2))->resetAsLazyProxy($obj2, function ($obj2) {
var_dump("initializer");
});
$obj3 = (new ReflectionClass(C::class))->newLazyGhost(function () {
var_dump("initializer");
});
test('Proxy', $obj, $obj2, $obj3);
--EXPECTF--
# Ghost:
bool(false)
object(C)#%d (0) {
}
bool(false)
object(D)#%d (0) {
}
bool(false)
object(C)#%d (0) {
}
# Proxy:
bool(false)
object(C)#%d (0) {
}
bool(false)
object(D)#%d (0) {
}
bool(false)
object(C)#%d (0) {
}

View File

@ -0,0 +1,91 @@
--TEST--
Lazy objects: Object is not lazy anymore if all props have been assigned a value (overridden prop)
--FILE--
<?php
class B {
public readonly string $b;
public function __construct() {
$this->b = 'b';
}
}
class C extends B {
public string $a;
public readonly string $b;
public function __construct() {
parent::__construct();
$this->a = 'a';
}
}
function test(string $name, object $obj) {
$reflector = new ReflectionClass(C::class);
printf("# %s:\n", $name);
var_dump(!$reflector->isUninitializedLazyObject($obj));
$reflector->getProperty('a')->setRawValueWithoutLazyInitialization($obj, 'a1');
var_dump(!$reflector->isUninitializedLazyObject($obj));
// Should not count a second prop initialization
$reflector->getProperty('a')->setRawValueWithoutLazyInitialization($obj, 'a2');
var_dump(!$reflector->isUninitializedLazyObject($obj));
try {
// Should not count a prop initialization
$reflector->getProperty('a')->setRawValueWithoutLazyInitialization($obj, new stdClass);
} catch (Error $e) {
printf("%s: %s\n", $e::class, $e->getMessage());
}
(new ReflectionProperty(B::class, 'b'))->setRawValueWithoutLazyInitialization($obj, 'b');
var_dump(!$reflector->isUninitializedLazyObject($obj));
var_dump($obj);
}
$reflector = new ReflectionClass(C::class);
$obj = $reflector->newLazyGhost(function ($obj) {
var_dump("initializer");
$obj->__construct();
});
test('Ghost', $obj);
$obj = $reflector->newLazyProxy(function ($obj) {
var_dump("initializer");
return new C();
});
test('Proxy', $obj);
--EXPECTF--
# Ghost:
bool(false)
bool(false)
bool(false)
TypeError: Cannot assign stdClass to property C::$a of type string
bool(true)
object(C)#%d (2) {
["b"]=>
string(1) "b"
["a"]=>
string(2) "a2"
}
# Proxy:
bool(false)
bool(false)
bool(false)
TypeError: Cannot assign stdClass to property C::$a of type string
bool(true)
object(C)#%d (2) {
["b"]=>
string(1) "b"
["a"]=>
string(2) "a2"
}

View File

@ -0,0 +1,101 @@
--TEST--
Lazy objects: Object is not lazy anymore if all props have been skipped
--FILE--
<?php
#[AllowDynamicProperties]
class B {
private readonly string $b;
public function __construct() {
$this->b = 'b';
}
}
#[AllowDynamicProperties]
class C extends B {
public string $a;
public function __construct() {
parent::__construct();
$this->a = 'a';
}
}
function test(string $name, object $obj) {
$reflector = new ReflectionClass(C::class);
printf("# %s:\n", $name);
var_dump(!$reflector->isUninitializedLazyObject($obj));
$reflector->getProperty('a')->skipLazyInitialization($obj);
var_dump(!$reflector->isUninitializedLazyObject($obj));
// Should not count a second prop initialization
$reflector->getProperty('a')->skipLazyInitialization($obj);
var_dump(!$reflector->isUninitializedLazyObject($obj));
try {
// Should not count a prop initialization
$reflector->getProperty('xxx')->skipLazyInitialization($obj);
} catch (ReflectionException $e) {
printf("%s: %s\n", $e::class, $e->getMessage());
}
try {
// Should not count a prop initialization
$reflector->getProperty('b')->skipLazyInitialization($obj);
} catch (ReflectionException $e) {
printf("%s: %s\n", $e::class, $e->getMessage());
}
(new ReflectionProperty(B::class, 'b'))->skipLazyInitialization($obj);
var_dump(!$reflector->isUninitializedLazyObject($obj));
var_dump($obj);
}
$reflector = new ReflectionClass(C::class);
$obj = $reflector->newLazyGhost(function ($obj) {
var_dump("initializer");
$obj->__construct();
});
test('Ghost', $obj);
$obj = $reflector->newLazyProxy(function ($obj) {
var_dump("initializer");
return new C();
});
test('Proxy', $obj);
--EXPECTF--
# Ghost:
bool(false)
bool(false)
bool(false)
ReflectionException: Property C::$xxx does not exist
ReflectionException: Property C::$b does not exist
bool(true)
object(C)#%d (0) {
["b":"B":private]=>
uninitialized(string)
["a"]=>
uninitialized(string)
}
# Proxy:
bool(false)
bool(false)
bool(false)
ReflectionException: Property C::$xxx does not exist
ReflectionException: Property C::$b does not exist
bool(true)
object(C)#%d (0) {
["b":"B":private]=>
uninitialized(string)
["a"]=>
uninitialized(string)
}

View File

@ -0,0 +1,28 @@
--TEST--
Lazy objects: resetAsLazy*() accept a sub-class of the reflected class
--FILE--
<?php
class A {
public $a;
}
class B extends A {}
class C {}
$reflector = new ReflectionClass(A::class);
$reflector->resetAsLazyGhost(new A(), function () {});
$reflector->resetAsLazyGhost(new B(), function () {});
try {
$reflector->resetAsLazyGhost(new C(), function () {});
} catch (TypeError $e) {
printf("%s: %s\n", $e::class, $e->getMessage());
}
?>
==DONE==
--EXPECT--
TypeError: ReflectionClass::resetAsLazyGhost(): Argument #1 ($object) must be of type A, C given
==DONE==

View File

@ -0,0 +1,56 @@
--TEST--
Lazy objects: resetAsLazy*() on already lazy object is not allowed
--FILE--
<?php
class C extends stdClass {
public int $a;
}
$reflector = new ReflectionClass(C::class);
printf("# Ghost:\n");
$obj = new C();
$reflector->resetAsLazyGhost($obj, function () {});
try {
$reflector->resetAsLazyGhost($obj, function ($obj) {
});
} catch (\Exception $e) {
printf("%s: %s\n", $e::class, $e->getMessage());
}
printf("# Proxy:\n");
$obj = new C();
$reflector->resetAsLazyProxy($obj, function () {});
try {
$reflector->resetAsLazyProxy($obj, function ($obj) {
});
} catch (\Exception $e) {
printf("%s: %s\n", $e::class, $e->getMessage());
}
$obj = new C();
$reflector->resetAsLazyProxy($obj, function () {
return new C();
});
$reflector->initializeLazyObject($obj);
try {
$reflector->resetAsLazyProxy($obj, function ($obj) {
});
} catch (\Exception $e) {
printf("%s: %s\n", $e::class, $e->getMessage());
}
?>
==DONE==
--EXPECT--
# Ghost:
ReflectionException: Object is already lazy
# Proxy:
ReflectionException: Object is already lazy
==DONE==

View File

@ -0,0 +1,61 @@
--TEST--
Lazy objects: resetAsLazy*() calls destructor of pre-existing object
--FILE--
<?php
class C {
public readonly int $a;
public function __construct() {
$this->a = 1;
}
public function __destruct() {
var_dump(__METHOD__);
}
}
$reflector = new ReflectionClass(C::class);
print "# Ghost:\n";
$obj = new C();
print "In makeLazy\n";
$reflector->resetAsLazyGhost($obj, function ($obj) {
var_dump("initializer");
$obj->__construct();
});
print "After makeLazy\n";
var_dump($obj->a);
$obj = null;
print "# Proxy:\n";
$obj = new C();
print "In makeLazy\n";
$reflector->resetAsLazyProxy($obj, function ($obj) {
var_dump("initializer");
return new C();
});
print "After makeLazy\n";
var_dump($obj->a);
$obj = null;
?>
--EXPECT--
# Ghost:
In makeLazy
string(13) "C::__destruct"
After makeLazy
string(11) "initializer"
int(1)
string(13) "C::__destruct"
# Proxy:
In makeLazy
string(13) "C::__destruct"
After makeLazy
string(11) "initializer"
int(1)
string(13) "C::__destruct"

Some files were not shown because too many files have changed in this diff Show More