Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

[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

Merged

Conversation

@nicolas-grekas
Copy link
Member

@nicolas-grekasnicolas-grekas commentedJun 23, 2022
edited
Loading

QA
Branch?6.2
Bug fix?no
New feature?yes
Deprecations?no
Tickets-
LicenseMIT
Doc PR-

EDIT: the trait used to be namedLazyGhostObjectTrait but it's been renamed toLazyGhostTrait in#47236, where a newLazyProxyTrait has also been added.

This PR packages an implementation oflazy loading ghost objects in a singleLazyGhostObjectTrait (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):

  1. lazy proxy generation for service containers;
  2. lazy proxy generation for entities.

In all cases, the generation itself is trivial using inheritance (sorryfinal classes.) For example, in order to turn aFoo class into a lazy ghost object, one just needs to do:

class FooGhostextends Fooimplements LazyGhostObjectInterface{use LazyGhostObjectTrait;}

And then, one can instantiate ghost objects like this:

$fooGhost = FooGhost::createLazyGhostObject($initializer);

$initializer should 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:

$initializer =function ($instance)use ($etc) {// [...] use $etc to compute the $deps$instance->__construct(...$deps);};

InterfaceLazyGhostObjectInterface is optional to get the behavior of a ghost object but gives a contract that allows managing them when needed:

publicfunction initializeLazyGhostObject():void;publicfunction resetLazyGhostObject():bool;

Because initializers arenot freed when initializing, it's possible to reset a ghost object to its uninitialized state. This comes with one limitation: resettingreadonly properties 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 withreadonly properties, but also to allow creating partially initialized objects,$initializer can also accept two more arguments$propertyName and$propertyScope. When doing so,$initializer is 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 theVarExporter component? 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/

lyrixx reacted with hooray emojimykiwi and maxhelias reacted with rocket emoji
@Tobion
Copy link
Contributor

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/

How do you plan to do this integration? I assume devs need to mark a service as lazy and alsouse LazyGhostObjectTrait in their class. Or do you want to do code generation for lazy marked services that implements automatically?

class FooGhost extends Foo implements LazyGhostObjectInterface{    use LazyGhostObjectTrait;}

@nicolas-grekas
Copy link
MemberAuthor

@Tobion this is already implemented, you might want to check#46752

@nicolas-grekasnicolas-grekasforce-pushed theve-ghost-objects branch 3 times, most recently from17e0838 to798ba60CompareJune 30, 2022 13:18
@nicolas-grekas
Copy link
MemberAuthor

nicolas-grekas commentedJun 30, 2022
edited
Loading

I reworked the implementation to allow partial initialization. There are two ways to achieve it:

  • the$initializer can optionally accept two more arguments$propertyName and$propertyScope. When doing so, it is 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.

PR description + README updated.

@nicolas-grekasnicolas-grekasforce-pushed theve-ghost-objects branch 7 times, most recently from6971e57 tof5d45ddCompareJuly 5, 2022 07:56
@nicolas-grekas
Copy link
MemberAuthor

/cc @symfony/mergers this PR is ready :)

nicolas-grekas added a commit that referenced this pull requestJul 5, 2022
…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
@nicolas-grekasnicolas-grekas merged commit338daf2 intosymfony:6.2Jul 12, 2022
nicolas-grekas added a commit that referenced this pull requestJul 12, 2022
…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
@nicolas-grekasnicolas-grekas deleted the ve-ghost-objects branchJuly 12, 2022 16:17
fabpot added a commit that referenced this pull requestSep 3, 2022
…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
@fabpotfabpot mentioned this pull requestOct 24, 2022
Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment

Reviewers

@stofstofstof approved these changes

+2 more reviewers

@stloydstloydstloyd left review comments

@TobionTobionTobion approved these changes

Reviewers whose approvals may not affect merge requirements

Assignees

No one assigned

Projects

None yet

Milestone

6.2

Development

Successfully merging this pull request may close these issues.

5 participants

@nicolas-grekas@Tobion@stloyd@stof@carsonbot

[8]ページ先頭

©2009-2025 Movatter.jp