Introduce lazy object and proxy object support helpers
#57831
+701
−0
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
PHP's new lazy objects are very cool and useful for keeping the memory footprint low and improving performance in some use cases.
Unfortunately they are also super clunky to use.
Example of using the native API to create a ghost and a proxy:
Clunky?
Yes. Clunky.
__constructwhen creating a ghost vs returning a new instance when creating a proxy.$object->__constructfeels nasty.This PR introduces two new support helpers,
lazyandproxy, in hopes to make using these features more ergonomic.Re-worked example using the proposed helpers:
You may be thinking Hey, this is an older style Laravel API. We should be able to pass a single closure through like so:
If that is you, please hold back your feelings until the end. For now, I'll stick with the
lazy($class, $callback)API.Eagerly setting properties
Ghost objects allow you to eagerly set properties. Using the native APIs we would need the following:
The
lazyhelper allows you to pass eager values through as a named parameter using a hash map:Naming
I've opted for
lazyoverghostas a function name, even though PHP calls them ghosts.ghostmakes me feel like I'm gonna have to look up the difference between a ghost and a proxy every time I reach for them.lazyvsproxyfeels more clearer to me.lazyin the framework for similar things.Full API examples
Lazy
lazyis the go to when you are in full control of creating the object, e.g., if you would otherwise callnew Resultyourself, whether the class is in your namespace or a vendor namespace.Even with the more verbose escape hatch, it feels much more ergonomic to me. A comparison:
Proxy
proxyis what you would use for if you are not in control of instantiating the object and a 3rd party is in charge of creating the object. You can make make their instantiation logic lazy:Supplemental APIs
In similar APIs, Laravel has opted for determining the type from the closure. Although this is possible, I propose that we offer this as a secondary API, rather than the primarily documented API. I think it is fair to assume some people will attempt this API, based on previous Laravel experience, so it makes sense to support it. I'll outline below why I don't think it is a great primary API, though.
Given that majority of use-cases, in my opinion, are going to return an array of arguments, being forced to pass through the object as a variable to then never use does not feel nice:
Notice we never reference
$instanceor$proxyvariables in the closure. Sure, we could do take a leaf out of the JS book and use$_but it still feels off to me:I love other APIs that do this, but in all of those cases I want the thing I'm accepting. That isn't really the case with these helpers.
Comparing side-by-side for a vibes check:
The only time this particular API is an improvement on the suggested primary API is for the
lazyhelper when you need to call additional APIs when constructing, which also feels like a usage outlier:What if I only have a single argument to pass?
I went back and forth on this a bit, e.g., offering the ability to accept a single argument rather than an array:
If feel this adds ambiguity. What if I want to accept a single argument that is an array? You still need to wrap it in an array. Because of this, I felt keeping it simple and requiring a wrapping array was the best approach.