Skip to content
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 our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[5.7] Track abstracts being resolved, fire resolving events once #23701

Closed
wants to merge 6 commits into from

Conversation

tomlankhorst
Copy link
Contributor

Fixes #23699 by tracking abstracts that are currently being resolved.
If an object is built that is an instanceof of an object that is already on the stack, firing the resolving() callbacks is inhibited.
These callbacks are fired eventually when the instanceof object is resolved.

@tomlankhorst tomlankhorst changed the title Track abstracts being resolved #23699 Track abstracts being resolved, fire resolving events once #23699 Mar 26, 2018
@tomlankhorst
Copy link
Contributor Author

tomlankhorst commented Mar 26, 2018

Put this on master, btw, because it changes (expected?) behavior and that might break existing apps.

@sisve
Copy link
Contributor

sisve commented Mar 26, 2018

  1. There's a missing array_pop in the resolve() method for the early return.
  2. These changes will still trigger the resolving-event for the class twice.

interface LoggerInterface {}

class LoggerImpl implements LoggerInterface {
    function __construct() {
        dump('logger constructor');
    }
}

class DepA {
    function __construct(LoggerInterface $logger) {}
}

class DepB {
    function __construct(LoggerInterface $logger) {}
}

class Service {
    function __construct(DepA $a, DepB $b, LoggerInterface $logger) {}
}

app()->singleton(LoggerInterface::class, LoggerImpl::class);
app()->resolving(LoggerInterface::class, function() {
    dump('resolving iface');
});
app()->resolving(LoggerImpl::class, function() {
    dump('resolving class');
});
app()->afterResolving(LoggerInterface::class, function() {
    dump('resolved iface');
});
app()->afterResolving(LoggerImpl::class, function() {
    dump('resolved class');
});
$service = app(Service::class);

The output is ...

"logger constructor" // separate weirdness, shouldn't this be between resolving and resolved?
"resolving iface" // removed by this pr
"resolving class" // still triggered with this pr
"resolved iface" // removed by this pr
"resolved class" // still triggered with this pr
"resolving iface"
"resolving class"
"resolved iface"
"resolved class"

The fact that the resolving event is triggered after the class has resolved is weird. I didn't expect that all.

@tomlankhorst
Copy link
Contributor Author

tomlankhorst commented Mar 26, 2018

You're totally right on the missing array_pop.
The resolving and afterResolving callbacks are all called in fireResolvingCallbacks(), after the construction of the object, before injecting it in the depending class' constructor.

Looking into your second remark...

@tomlankhorst
Copy link
Contributor Author

tomlankhorst commented Mar 26, 2018

I added a method that checks whether an object or a base class / interface is pending in the resolveStack. Then, the callback will skip and it will fire eventually when the last pending resolve call finishes.

So, for a Bar class that implements IFoo, that looks like this:

  • app( IFoo::class ) resolves IFoo::class
  • IFoo::class is bound to Bar::class
  • app( IFoo::class ) resolves Bar::class
    • Bar::class is resolved: object is an instance of Bar
    • Bar's resolving callback is inhibited because IFoo - of which Bar is an instanceof - is in the resolveStack
  • IFoo resolves
  • IFoo triggers callbacks on the resolved object, an instance of IBar, and triggers resolving callbacks both on IFoo and Bar

@GrahamCampbell GrahamCampbell changed the title Track abstracts being resolved, fire resolving events once #23699 [5.7] Track abstracts being resolved, fire resolving events once Mar 26, 2018
@tomlankhorst
Copy link
Contributor Author

@sisve I've added the situation you describe to the test.

@laurencei
Copy link
Contributor

laurencei commented Mar 30, 2018

Before this is merged - can you run some scripts that creates like 10,000 Eloquent objects? What is the memory usage before/after this patch? And resolve loads of classes - what does that do?

The reason is - using arrays to track things was a cause to a bug I fixed a while ago in the container back in 5.4 here: #18812

I'm not saying your approach wont work - it probably does from the quick glance I did. But you gotta be very careful with arrays and the container and memory usage.

@tomlankhorst
Copy link
Contributor Author

tomlankhorst commented Mar 30, 2018

@laurencei Thanks for your comment. The $resolveStack is tracking an array of abstracts which are string types. Furthermore, the stack tracks currently resolving abstracts, not all abstracts.
So, the memory usage of this stack would have a maximum that scales linearly with the deepest dependency level in the application, which I expect would be rarely that great.

Simplified demo: https://3v4l.org/cEoe4

@taylorotwell
Copy link
Member

This loop at every step of resolution is a little concerning. Could be a performance problem.

@tomlankhorst
Copy link
Contributor Author

I'll assess the performance impact.

@taylorotwell
Copy link
Member

Closing pending inactivity on performance reports.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants