Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork9.7k
[VarExporter] Add trait to help implement lazy loading ghost objects#46751
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
Uh oh!
There was an error while loading.Please reload this page.
Conversation
f0d3a20 to43dfc9bCompare1b739d0 to135fc98CompareTobion commentedJun 24, 2022
How do you plan to do this integration? I assume devs need to mark a service as lazy and also |
nicolas-grekas commentedJun 24, 2022
src/Symfony/Component/VarExporter/Tests/Fixtures/LazyGhostObject/MagicClass.php OutdatedShow resolvedHide resolved
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
17e0838 to798ba60Comparenicolas-grekas commentedJun 30, 2022 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
I reworked the implementation to allow partial initialization. There are two ways to achieve it:
PR description + README updated. |
6971e57 tof5d45ddCompareUh oh!
There was an error while loading.Please reload this page.
nicolas-grekas commentedJul 5, 2022
/cc @symfony/mergers this PR is ready :) |
…ce (nicolas-grekas)This PR was merged into the 6.2 branch.Discussion----------[VarExporter] Fix accessing readonly properties by reference| Q | A| ------------- | ---| Branch? | 6.2| Bug fix? | yes| New feature? | no| Deprecations? | no| Tickets | -| License | MIT| Doc PR | -Extracted from#46751Should make tests green on PHP 8.2.Commits-------46ca03e [VarExporter] Fix accessing readonly properties by reference
f5d45dd toaf1612aCompareUh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
src/Symfony/Component/VarExporter/Internal/GhostObjectRegistry.php OutdatedShow resolvedHide resolved
Uh oh!
There was an error while loading.Please reload this page.
src/Symfony/Component/VarExporter/Tests/LazyGhostObjectTraitTest.php OutdatedShow resolvedHide resolved
Uh oh!
There was an error while loading.Please reload this page.
src/Symfony/Component/VarExporter/Tests/LazyGhostObjectTraitTest.php OutdatedShow resolvedHide resolved
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
af1612a tocf62f1aComparecf62f1a to27b4325Compare…oxies out of the box (nicolas-grekas)This PR was merged into the 6.2 branch.Discussion----------[DependencyInjection] Use lazy-loading ghost object proxies out of the box| Q | A| ------------- | ---| Branch? | 6.2| Bug fix? | no| New feature? | yes| Deprecations? | no| Tickets |Fix#35345| License | MIT| Doc PR | -This PR builds on#46751. It also replaces#46458.Instead of using ProxyManager to make lazy services actually lazy, using `LazyGhostObjectTrait` from#46751 allows doing so *out of the box* - aka without the need to install any optional dependencies.When a virtual proxy is required (typically when using [the `proxy` tag](#27697)), ProxyManager is still required (and the dep remains optional.)But for most services, using `LazyGhostObjectTrait` just works \o/Commits-------58a1848 [DependencyInjection] Use lazy-loading ghost object proxies out of the box
…ng virtual proxies for non-ghostable lazy services (nicolas-grekas)This PR was merged into the 6.2 branch.Discussion----------[DependencyInjection][VarExporter] Generate lazy-loading virtual proxies for non-ghostable lazy services| Q | A| ------------- | ---| Branch? | 6.2| Bug fix? | no| New feature? | yes| Deprecations? | no| Tickets | -| License | MIT| Doc PR | -Since#46752 and#46751, we are able to make services lazy out of the box, except when 1. a service relies on an internal class 2. a service has the `proxy` tag or 3. a service's class is abstract (and the service uses a factory). In these situations, proxy-manager-bridge was required. This was an acceptable trade-off because this would be quite uncommon. But while working on Doctrine, I realized that we cannot use ghost objects when a factory is used. I described this for Doctrine indoctrine/orm#9896 but the situation can happen with any services constructed by a factory. This means we'd need proxy-manager-bridge anytime a factory is used on a lazy service. What was uncommon becomes quite common and the trade-off is not acceptable anymore. Thus this PR.This PR adds a `LazyProxyTrait` and a `ProxyHelper` to build lazy loading virtual proxies at will. It then wires this new capability into the container. As a result proxy-manager-bridge is not needed anymore. We can deprecate it in another PR.While the diff is quite big, `LazyProxyTrait` has many similarities with `LazyGhostTrait` and both traits can be diffed to see where their behavior varies.Excerpt from the [README](https://github.com/nicolas-grekas/symfony/blob/ve-inheritance-proxy/src/Symfony/Component/VarExporter/README.md) for the record:>The component provides two lazy loading patterns: ghost objects and virtual proxies (seehttps://martinfowler.com/eaaCatalog/lazyLoad.html for reference.)>>Ghost objects work only on concrete and non-internal classes. In the generic case, they are not compatible with using factories in their initializer.>>Virtual proxies work on concrete, abstract or internal classes. They provide an API that looks like the actual objects and forward calls to them. They can cause identity problems because proxies might not be seen as equivalents to the actual objects.>>Because of this identity problem, ghost objects should be preferred when possible. Exceptions thrown by the ProxyHelper class can help decide when it can be used or not.>>Ghost objects and virtual proxies both provide implementations for the LazyObjectInterface which allows resetting them to their initial state or to forcibly initialize them when needed. Note that resetting a ghost object skips its read-only properties. You should use a virtual proxy to reset read-only properties.Commits-------4862139 [DependencyInjection][VarExporter] Generate lazy proxies for non-ghostable lazy services out of the box
Uh oh!
There was an error while loading.Please reload this page.
EDIT: the trait used to be named
LazyGhostObjectTraitbut it's been renamed toLazyGhostTraitin#47236, where a newLazyProxyTraithas also been added.This PR packages an implementation oflazy loading ghost objects in a single
LazyGhostObjectTrait(as a reminder, a lazy ghost object is an object that is created empty and that is able to initialize itself when being accessed for the first time.)By using this trait, ppl can easily turn any existing classes into such ghost object implementations.
I target two use cases with this feature (but ppl are free to be more creative):
In all cases, the generation itself is trivial using inheritance (sorry
finalclasses.) For example, in order to turn aFooclass into a lazy ghost object, one just needs to do:And then, one can instantiate ghost objects like this:
$initializershould be a closure that takes the ghost object instance as argument and initializes it. An initializer would typically call the constructor on the instance after resolving its dependencies:Interface
LazyGhostObjectInterfaceis optional to get the behavior of a ghost object but gives a contract that allows managing them when needed:Because initializers arenot freed when initializing, it's possible to reset a ghost object to its uninitialized state. This comes with one limitation: resetting
readonlyproperties is not allowed by the engine so these cannot be reset. The main target use case of this capability is Doctrine's EntityManager of course.To work around the limitation with
readonlyproperties, but also to allow creating partially initialized objects,$initializercan also accept two more arguments$propertyNameand$propertyScope. When doing so,$initializeris going to be called on a property-by-property basis and is expected to return the computed value of the corresponding property.Because lazy-initialization isnot triggered when (un)setting a property, it's also possible to do partial initialization by calling setters on a just-created ghost object.
You might wonder why this PR is in the
VarExportercomponent? The answer is that it reuses a lot of its existing code infrastructure. Exporting/hydrating/instantiating require using reflection a lot, and ghost objects too. We could consider renaming the component, but honestly, 1. I don't have a good name in mind; 2. changing the name of a component is costly for the community and 3. more importantly this doesn't really matter because this is all low-level stuff usually.You might also wonder why this trait while we already havehttps://github.com/FriendsOfPHP/proxy-manager-lts andhttps://github.com/Ocramius/ProxyManager?
The reason is that the code infrastructure in ProxyManager is heavy. It comes with a dependency onhttps://github.com/laminas/laminas-code and it's complex to maintain. While I made the necessary changes to support PHP 8.1 in FriendsOfPHP/proxy-manager-lts (and submitted those changesupstream), getting support for new PHP versions is slow and complex. Don't take me wrong, I don't blame maintainers, ProxyManager is complex for a reason.
But ghost objects are way simpler than other kind of proxies that ProxyManager can produce: a trait does the job. While the trait itself is no trivial logic, it's at least plain PHP code, compared to convoluted (but needed) code generation logic in ProxyManager.
If you need any other kind of proxies that ProxyManager supports, just use ProxyManager.
For Symfony, having a simple lazy ghost object implementation will allow services declared as lazy to be actually lazy out of the box (today, you need to install proxy-manager-bridge as an optional dependency.) \o/