Skip to content
This repository has been archived by the owner on Jul 16, 2021. It is now read-only.

[Proposal] Lazy loading services when using dependency injection #1436

Open
vv12131415 opened this issue Dec 11, 2018 · 7 comments
Open

[Proposal] Lazy loading services when using dependency injection #1436

vv12131415 opened this issue Dec 11, 2018 · 7 comments

Comments

@vv12131415
Copy link

vv12131415 commented Dec 11, 2018

The why do we need it can be found here (tl;dr to not (fully) construct objects with dependency injection container when we don't use them, to defer their resolve to the moment when we will need them)

The idea is to implement something that will be similar to

The zend version and symfony uses the https://github.com/Ocramius/ProxyManager to achieve the goal

Some of the ideas of how to implement it in laravel you can see here (they are kinda old, but still )

How it was implemented in symfony and zend (the PRs)

@vv12131415 vv12131415 changed the title Lazy loading services when using dependency injection [Proposal]Lazy loading services when using dependency injection Dec 17, 2018
@vv12131415 vv12131415 changed the title [Proposal]Lazy loading services when using dependency injection [Proposal] Lazy loading services when using dependency injection Dec 17, 2018
@d3jn
Copy link

d3jn commented Jan 9, 2019

Any progress on implementing such a feature so far?

@vv12131415
Copy link
Author

Any progress on implementing such a feature so far?

I have not yet even started, not sure if it can be as separate package

@taylorotwell
Copy link
Member

I think we would need really good benchmarks on how much of a difference this even makes. I think it would be negligible on a standard web app. Laravel already allows method injection at the controller level with the container.

@vv12131415
Copy link
Author

I think we would need really good benchmarks on how much of a difference this even makes. I think it would be negligible on a standard web app.

So I need to implement it and then benchmark it, is that what you mean?

Laravel already allows method injection at the controller level with the container.

yes, but that not exactly what I what since I want it in my own code that is kind of frameworkless (I mean simple POPOs).

  • What I can do is call $object = $this->container->make(MyRandomPopo::class); $object->callRandomMethod() and it will resolve all the constructor dependencies, but this way it will resolve all of them even if I don't need them all.
  • I can do $this->container->call([MyRandomPopo::class, 'callRandomMethod']) (as you probably suggest) but this way you will relay on a stringly-typed API (so there is a change that you be wrong when typing callRandomMethod and it will fail in runtime and also static analyses wouldn't understand what this code means)
  • use app/resolve helper, but that's service locator, and I what to avoid it.

the other way to do it is separate the Application and Container and then decorate the Application instance, but, as I think it's a lot of work to do, but this way it don't needs to be in the core

@taylorotwell
Copy link
Member

I kinda think you're premature optimizing here a bit.

@vv12131415
Copy link
Author

@taylorotwell
I'm now in case, where I have circular dependency without this.
Maybe you changed your mind about this feature?

@githubeing
Copy link

@vladyslavstartsev a simple workaround to resolve circular dependencies is to introduce a service factory, e.g.:

<?php

namespace Practice\Tests\Unit;

use Closure;
use Illuminate\Container\Container;

class Foo1
{
    private Bar1 $bar;

    public function __construct(Bar1 $bar)
    {
        $this->bar = $bar;
    }

    public function hello()
    {
    }
}

class Bar1
{
    private Foo1 $foo;

    public function __construct(Foo1 $foo)
    {
        $this->foo = $foo;
    }

    public function world()
    {
        $this->foo->hello();
    }
}

class Foo2
{
    private Bar2 $bar;

    public function __construct(Bar2 $bar)
    {
        $this->bar = $bar;
    }

    public function hello()
    {
    }
}

class Bar2
{
    private Closure $fooFactory;
    private Foo2 $foo;

    public function __construct(Closure $fooFactory)
    {
        $this->fooFactory = $fooFactory;
    }

    public function world()
    {
        $this->foo()->hello();
    }

    private function foo(): Foo2
    {
        if (!isset($this->foo)) {
            $this->foo = ($this->fooFactory)();
        }
        return $this->foo;
    }
}

class CircularTest extends TestCase
{
    public function testCircularFails()
    {
        $container = new Container();
        $this->expectExceptionMessageMatches("/Maximum function nesting level of '\\d+' reached, aborting!/");
        $container->get(Bar1::class)->world();
    }

    public function testFactorySucceeds()
    {
        $container = new Container();
        $container->bind(Bar2::class, fn() => new Bar2(fn() => $container->get(Foo2::class)));
        $container->get(Bar2::class)->world();
        self::assertTrue(true);
    }
}

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

No branches or pull requests

4 participants