延迟对象是指其初始化推迟到状态被观察或修改时才进行的对象。一些用例示例包括依赖项注入组件(仅在需要时提供完全初始化的延迟服务)、ORM(提供仅在访问时才从数据库获取数据的延迟实体)或 JSON 解析器(延迟解析直到访问元素)。
支持两种延迟对象策略:幽灵对象(Ghost Object)和虚拟代理(Virtual Proxies),以下称为"延迟幽灵"和"延迟代理"。 在这两种策略中,延迟对象都附加到初始化程序或工厂,当第一次观察或修改其状态时会自动调用。从抽象的角度来看,延迟幽灵对象与非延迟幽灵对象没有区别:都可以在不知道自己是延迟的情况下使用,从而允许将其传递给不知道延迟的代码并由其使用。延迟代理同样是透明的,但在使用它们的标识(identity)时必须小心,因为代理和其真实实例具有不同的标识。
可以创建任何用户定义类或 stdClass 类的延迟实例(不支持其他内部类),或重置这些类的实例以使其成为延迟实例。创建延迟对象的入口点是 ReflectionClass::newLazyGhost() 和 ReflectionClass::newLazyProxy() 方法。
这两种方法都接受函数,在对象需要初始化时调用该函数。该函数的预期行为因所使用的策略而异,如每种方法的参考文档中所述。
示例 #1 创建延迟幽灵
<?php
class Example
{
public function __construct(public int $prop)
{
echo __METHOD__, "\n";
}
}
$reflector = new ReflectionClass(Example::class);
$lazyObject = $reflector->newLazyGhost(function (Example $object) {
// 在这里初始化对象
$object->__construct(1);
});
var_dump($lazyObject);
var_dump(get_class($lazyObject));
// 触发初始化
var_dump($lazyObject->prop);
?>
以上示例会输出:
lazy ghost object(Example)#3 (0) { ["prop"]=> uninitialized(int) } string(7) "Example" Example::__construct int(1)
示例 #2 创建延迟代理
<?php
class Example
{
public function __construct(public int $prop)
{
echo __METHOD__, "\n";
}
}
$reflector = new ReflectionClass(Example::class);
$lazyObject = $reflector->newLazyProxy(function (Example $object) {
// 创建并返回真实实例
return new Example(1);
});
var_dump($lazyObject);
var_dump(get_class($lazyObject));
// Triggers initialization
var_dump($lazyObject->prop);
?>
以上示例会输出:
lazy proxy object(Example)#3 (0) { ["prop"]=> uninitialized(int) } string(7) "Example" Example::__construct int(1)
对延迟对象属性的任何访问都会触发其初始化(包括通过 ReflectionProperty 访问)。但是,某些属性可能是预先知道的,并且在访问时不应触发初始化:
示例 #3 立即初始化属性
<?php
class BlogPost
{
public function __construct(
public int $id,
public string $title,
public string $content,
) { }
}
$reflector = new ReflectionClass(BlogPost::class);
$post = $reflector->newLazyGhost(function ($post) {
$data = fetch_from_store($post->id);
$post->__construct($data['id'], $data['title'], $data['content']);
});
// 如果没有这行,下面调用 ReflectionProperty::setValue() 时将触发初始化
$reflector->getProperty('id')->skipLazyInitialization($post);
$reflector->getProperty('id')->setValue($post, 123);
// 或者直接使用它:
$reflector->getProperty('id')->setRawValueWithoutLazyInitialization($post, 123);
// 无需触发初始化即可访问 id 属性
var_dump($post->id);
?>
ReflectionProperty::skipLazyInitialization() 和 ReflectionProperty::setRawValueWithoutLazyInitialization() 方法提供了在访问属性时绕过延迟初始化的方法。
延迟幽灵,就地初始化的对象,并且一旦初始化,就跟从未延迟化的对象没有区别。当需要控制对象的实例化和初始化时,此策略是合适的,如果是其中任何一个由另一方管理,则不适合。
延迟代理一旦初始化,便充当真实实例的代理:对已初始化延迟代理的任何操作都将转发给真实实例。真实实例的创建可以委托给另一方,这使得此策略在延迟幽灵不适合的情况下非常有用。尽管延迟代理几乎与延迟幽灵一样透明,但在使用其身份时需要谨慎,因为代理和真实实例具有不同的身份。
对象可以使用 ReflectionClass::newLazyGhost() 或 ReflectionClass::newLazyProxy() 在实例化时设置为延迟,或者使用 ReflectionClass::resetAsLazyGhost() 或 ReflectionClass::resetAsLazyProxy() 在实例化后设置为延迟。之后可以通过下列操作之一初始化延迟对象:
由于延迟对象在所有属性均标记为非惰性时才会初始化,因此如果没有属性可以标记为延迟,则上述方法不会将对象标记为延迟。
延迟对象设计为对其使用者完全透明,因此观察或修改对象状态的常规操作将在执行操作之前自动触发初始化。这包括但不限于以下操作:
方法调用不访问对象状态的话不会触发初始化。同样,如果这些方法或函数不访问对象的状态,则调用魔术方法或挂钩函数与对象的交互也不会触发初始化。
以下特定方法或低级操作允许访问或修改延迟对象而无需触发初始化:
ReflectionClass::SKIP_INITIALIZATION_ON_SERIALIZE
时使用
serialize(),除非使用 __serialize()
或 __sleep() 触发初始化。
本节概述了根据所使用的执行策略触发初始化时的操作顺序。
null
or no value. The object is no
longer lazy at this point, so the function can access its properties
directly.
After initialization, the object is indistinguishable from an object that was never lazy.
After initialization, accessing any property on the proxy will yield the same result as accessing the corresponding property on the real instance; all property accesses on the proxy are forwarded to the real instance, including declared, dynamic, non-existing, or properties marked with ReflectionProperty::skipLazyInitialization() or ReflectionProperty::setRawValueWithoutLazyInitialization().
The proxy object itself is not replaced or substituted for the real instance.
While the factory receives the proxy as its first parameter, it is not expected to modify it (modifications are allowed but will be lost during the final initialization step). However, the proxy can be used for decisions based on the values of initialized properties, the class, the object itself, or its identity. For instance, the initializer might use an initialized property's value when creating the real instance.
The scope and $this context of the initializer or factory function remains unchanged, and usual visibility constraints apply.
After successful initialization, the initializer or factory function is no longer referenced by the object and may be released if it has no other references.
If the initializer throws an exception, the object state is reverted to its pre-initialization state and the object is marked as lazy again. In other words, all effects on the object itself are reverted. Other side effects, such as effects on other objects, are not reverted. This prevents exposing a partially initialized instance in case of failure.
克隆延迟对象会在创建克隆之前触发其初始化,从而产生已初始化的对象。
对于代理对象,代理及其真实实例都会克隆,并返回代理的克隆。__clone
方法会在真实实例上调用,而不是在代理上调用。克隆的代理和真实实例在初始化时会保持连接,因此对代理克隆的访问将转发到克隆的真实实例。
此行为可确保克隆对象和原始对象保持不同的状态。克隆后对原始对象或其初始化程序状态的更改不会影响克隆。克隆代理及其真实实例(而不是仅返回真实实例的克隆),可确保克隆操作始终返回同一类的对象。
对于延迟幽灵,只有对象初始化后才会调用析构方法。对于延迟代理,只有存在真实实例时才会调用析构方法。
ReflectionClass::resetAsLazyGhost() 和 ReflectionClass::resetAsLazyProxy() 方法可能会调用已重置对象的析构方法。