Hi
I finally got around to giving the RFC another read. Please apologize if
this email asks questions that have already been answered elsewhere, as
the current mailing list volume makes it hard for me to keep up.
On 6/14/24 14:13, Arnaud Le Blanc wrote:
Is there any reason to call the makeLazyX() methods on an object that
was not just freshly created with ->newInstanceWithoutConstructor()
then?
There are not many reasons to do that. The only indented use-case that
doesn't involve an object freshly created with
->newInstanceWithoutConstructor() is to let an object manage its own
laziness by making itself lazy in its constructor:
Okay. But the RFC (and your email) does not explain why I would want do
that. It appears that much of the RFC's complexity (e.g. around readonly
properties and destructors) stems from the wish to support turning an
existing object into a lazy object. If there is no strong reason to
support that, I would suggest dropping that. It could always be added in
a future PHP version.
- The return value of the initializer has to be an instance of a parent
or a child class of the lazy-object and it must have the same properties.
Would returning a parent class not violate the LSP? Consider the
following example:
class A { public string $s; }
class B extends A { public function foo() { } }
$o = new B();
ReflectionLazyObject::makeLazyProxy($o, function (B $o) {
return new A();
});
$o->foo(); // works
$o->s = 'init';
$o->foo(); // breaks
$o->foo() calls B::foo() in both cases here, as $o is always the proxy
object. We need to double check, but we believe that this rule doesn't
break LSP.
I don't understand what happens with the 'A' object then, but perhaps
this will become clearer once you add the requested examples.
The 'A' object is what is called the "actual instance" in the RFC. $o
acts as a proxy to the actual instance: Any property access on $o is
forwarded to the actual instance A.
I've read the updated RFC and it's still not clear to me that returning
an arbitrary “actual instance” object is sound. Especially when private
properties - which for all intents and purposes are not visible outside
of the class - are involved. Consider the following:
class A {
public function __construct(
public string $property,
) {}
}
class B extends A {
public function __construct(
string $property,
private string $foo,
) { parent::__construct($property); }
public function getFoo() {
return $this->foo;
}
}
$r = new ReflectionClass(B::class);
$obj = $r->newLazyProxy(function ($obj) {
return new A('value');
});
var_dump($obj->property); // 'value'
var_dump($obj->getFoo()); // Implicitly accesses A::${'\0B\0foo'}
(i.e. the mangled B::$foo property)?
Now you might say that B does not have the same properties as A and
creating the proxy is not legal, but then the addition of a new private
property would immediately break the use of the lazy proxy, which
specifically is something that private properties should not be able to do.
Best regards
Tim Düsterhus