Laravel ioc automatic resolution - works from cont

2020-07-12 21:12发布

问题:

Namespaces omitted for brevity...

I have written the following service provider and registered in config/app.php:

class OfferServiceProvider extends ServiceProvider
{
    public function register()
    {
        $this->registerLossControlManager();
    }

    protected function registerLossControlManager()
    {
        $this->app->bind('LossControlInterface', 'LossControl');
    }
}

Here is my LossControlInterface

interface LossControlInterface
{
    /**
     * @param int $demandId
     * @param float $offerTotal
     * @param float $productTotal
     * @param null|int $partnerId
     * @return mixed
     */
    public function make($demandId, $offerTotal, $productTotal, $partnerId = null);

    /**
     * @return float
     */
    public function getAcceptableLoss();

    /**
     * @return bool
     */
    public function isAcceptable();

    /**
     * @return bool
     */
    public function isUnacceptable();

    /**
     * @return null
     */
    public function reject();
}

Now within the controller, I can inject the LossController as follows:

use LossControlInterface as LossControl;

class HomeController extends BaseController {
   public function __construct(LossControl $lossControl)
    {
        $this->lossControl = $lossControl;
    }

    public function getLossThresholds()
    {
        $lossControl = $this->lossControl->make(985, 1000, null);

        var_dump('Acceptable Loss: ' . $lossControl->getAcceptableLoss());
        var_dump('Actual Loss: ' . $lossControl->calculateLoss());
        var_dump('Acceptable? ' . $lossControl->isAcceptable());

    }
}

However if I try to dependency inject the LossControlInterface from within a custom class called by a command:

[2014-09-02 13:09:52] development.ERROR: exception 'ErrorException' with message 'Argument 11 passed to Offer::__construct() must be an instance of LossControlInterface, none given, called in /home/vagrant/Code/.../ProcessOffer.php on line 44 and defined' in /home/vagrant/Code/.../Offer.php:79

It appears as though I am unable to dependency inject the interface into a custom class, but I can when dependency injecting into a controller.

Any thoughts on what Im doing wrong or have omitted to get the automatic resolution working?

回答1:

The IoC is automatic within controllers, and you don't see the injection because Laravel handles the construction of controllers for you. When creating any other custom class by using the new keyword, you will still need to send in all of the parameters needed to it's constructor:

$myClass = new ClassWithDependency( app()->make('Dependency') );

You can hide this, to a degree, by funneling creation of your custom class through a service provider:

// Your service provider
public function register()
{
    $this->app->bind('ClassWithDependency', function($app) {
        return new ClassWithDependency( $app->make('Dependency') );
    });
}

Then just have the IoC make it whenever you need it:

$myClass = app()->make('ClassWithDepenency');

In your case, you can change your code to look like this:

private function setOffer(Offer $offer = null) {
    $this->processOffer    = $offer ?: 
        new Offer( app()->make('LossControlInterface') );
}

A perhaps cleaner approach could be to create a service provider and an OfferFactory which gets injected into your controller. The controller can then request the factory to create the offer whenever it needs one:

// Controller
public function __construct(OfferFactory $offerFactory)
{
    $this->offerFactory = $offerFactory;
}

public function setOffer(Offer $offer = null)
{
    $this->processOffer = $offer ?: $this->offerFactory->createOffer();
}

// OfferFactory
class OfferFactory
{
    public function createOffer()
    {
        return app()->make('Offer');
    }
}

This has the benefit of completely decoupling your controller from the logic behind the creation of the offer, yet allowing you to have a spot to add any amount of complexity necessary to the process of creating offers.



回答2:

In Laravel 5.2 the simplest solution for your particular problem would be to replace

new Offer();

with

App::make('Offer');

or even shorter

app('Offer');

which will use Laravel Container to take care of dependencies.

If however you want to pass additional parameters to the Offer constructor it is necessary to bind it in your service provider

App::bind('Offer', function($app, $args) {
    return new Offer($app->make('LossControl'), $args);
});

And voila, now you can write

app('Offer', [123, 456]);


回答3:

In laravel 5.4 (https://github.com/laravel/framework/pull/18271) you need to use the new makeWith method of the IoC container.

App::makeWith( 'App\MyNameSpace\MyClass', [ $id ] );

if you still use 5.3 or below, the above answers will work.