On Tue, Jun 4, 2024 at 10:23 PM Tim Düsterhus <t...@bastelstu.be> wrote: > > Hi > > On 6/4/24 14:28, Nicolas Grekas wrote: > > Please find all the details here: > > https://wiki.php.net/rfc/lazy-objects > > > > We look forward to your thoughts and feedback. > > I've gave the RFC three or four passes and I'm not quite sure if I > follow everything, here's a list of some questions / remarks that came > to mind, roughly ordered by the order of things appearing in the RFC. > > - "been tested successfully on the Doctrine and on the Symfony projects" > > Is there a PoC patch showcasing how the code would change / be > simplified for those pre-existing codebases? > > - int $options = 0 > > Not a fan of flag parameters that take a bitset, those provide for a > terrible DX due to magic numbers. Perhaps make this a regular (named) > parameter, or an list of enum LazyObjectOptions { case > SkipInitOnSerialize; }? > > - skipProperty() > > Not a fan of the method name, because it doesn't really say what it > does, without consulting the docs. Perhaps `skipInitializationFor()` or > similar? > > - setProperty() > > Not a fan of the method name, because it is not a direct counterpart to > `getProperty()`. Unfortunately I don't have a better suggestion. > > - The examples should be expanded and clarified, especially the one for > makeLazyProxy(): > > My understanding is that the $object that is passed to the first > parameter of makeLazyProxy() is completely replaced. Is this > understanding correct? What does that mean for spl_object_hash(), > spl_object_id()? What does this mean for WeakMap and WeakReference? What > does this mean for objects that are only referenced from within $object? > > Consider this example: > > class Foo { > public function __destruct() { echo __METHOD__; } > } > > class Bar { > public string $s; > public ?Foo $foo; > > public function __destruct() { echo __METHOD__; } > } > > $bar = new Bar(); > $bar->foo = new Foo(); > > ReflectionLazyObject::makeLazyProxy($bar, function (Bar $bar) { > $result = new Bar(); > $result->foo = null; > $result->s = 'init'; > return $result; > }); > > var_dump($bar->s); > > My understanding is that this will dump `string(4) "init"`. Will the > destructor of Foo be called? Will the destructor of Bar be called? > > - What happens if I make an object lazy that already has all properties > initialized? Will that be a noop? Will that throw? Will that create a > lazy object that will never automatically be initialized? > > - Cloning, unless __clone() is implemented and accesses a property. > > The semantics of cloning a lazy object should be explicitly spelled out > in the RFC, ideally with an example of the various edge cases (should > any exist). > > - Before calling the initializer, properties that were not initialized > with ReflectionLazyObject::skipProperty(), > ReflectionLazyObject::setProperty(), > ReflectionLazyObject::setRawProperty() are initialized to their default > value. > > Should skipProperty() also skip the initialization to the default value? > My understanding is that it allows skipping the initialization on > access, but when initialization actually happens it should probably be > set to a well-defined value, no? > > Am I also correct in my understanding that this should read "initialized > to their default value (if any)", meaning that properties without a > default value are left uninitialized? > > - If an exception is thrown while calling the initializer, the object is > reverted to its pre-initialization state and is still considered lazy. > > Does this mean that the initializer will be called once again when > accessing another property? Will the "revert to its pre-initialization" > work properly when you have nested lazy objects? An example would > probably help. > > - The initializer is called with the object as first parameter. > > What is the behavior of accessing the object properties, while the > initializer is active? Based on the examples, I assume it will not be > recursively called, similarly to how the hooks work? > > - The object is marked as non-lazy and the initializer is released. > > What does it mean for the initializer to be released? Consider the > following example: > > ReflectionLazyObject::makeLazyGhost($o, $init = function ($o) use > (&$init) { > $o->init = $init; > }); > > - 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 > > - The destructor of lazy non-initialized objects is not called. > > That sounds unsafe. Consider the following example: > > class Mutex { > public string $s; > public function __construct() { > // take lock > } > > public function __destruct() { > // release lock > } > } > > $m = new Mutex(); > ReflectionLazyObject::makeLazyGhost($m, function ($m) { > }); > unset($m); // will not release the lock. > > - Using the Manager::createManager() factory is not compatible with > ghost objects because their initializer requires initializing the ghost > object in place, > > I don't understand that example, because it doesn't actually use lazy > objects and thus I don't understand if Manager, Dispatcher, or both are > intended to be lazily initialized. It would help to rewrite the example > to use `makeLazyGhost()` and indicate with a comment in which cases the > problem would arise. > > - Backward Incompatible Changes > > There a technicality: The `ReflectionLazyObject` class name will no > longer be available to userland. > > Best regards > Tim Düsterhus
As someone who has had to maintain these proxies/ghosts before, this looks quite useful and powerful. I feel it has rather wonky syntax, but it is clearly better than the alternative of implementing it yourself. I'm also a huge fan that the syntax allows for usage/creation far and away from the definition/class itself. Good luck, and I hope it passes. Robert Landers Software Engineer Utrecht NL