laravel / ideas

Issues board used for Laravel internals discussions.
938 stars 28 forks source link

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

Open vv12131415 opened 5 years ago

vv12131415 commented 5 years ago

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)

d3jn commented 5 years ago

Any progress on implementing such a feature so far?

vv12131415 commented 5 years ago

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 commented 5 years ago

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 commented 5 years ago

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).

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 commented 5 years ago

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

vv12131415 commented 4 years ago

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

githubeing commented 3 years ago

@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);
    }
}