Laravel: Target is not instantiable while building

2019-06-09 23:02发布

问题:

Sorry if that question is duplicate, but I've read a lot of related question in StackOverflow but none solved my problem:

I created an Artisan command which worked and where I injected a Kafka client service as 1st param and a concrete class, BudgetsTransformer, as the 2nd parameter

class ConsumeBudgetsCommand extends Command {
    public function __construct(FKafka $kafkaClient, BudgetsTransformer $transformer)
    {
        $this->kafkaClient = $kafkaClient;
        $this->transformer = $transformer;

        parent::__construct();
    }
}

AppServiceProvider class looked like:

class AppServiceProvider extends ServiceProvider
{

    public function register()
    {
        $this->app->bind('kafka.client', function ($app) {
            return new \Weq\FKafka\FKafka();
        });

        $this->app->bind('budget.transformer', function ($app) {
            return new BudgetsTransformer();
        });

    }

    public function boot()
    {
        $this->app->bind('consume:budgets', function ($app) {
            return new ConsumeBudgetsCommand($app['kafka.client'], $app['budget.transformer']);
        });

        $this->commands('consume:budgets');
    }
}

So far all is working properly... Then I decided to create a TransformerInterface which BudgedTransformer implements (and other future Transformers will implement it):

class BudgetsTransformer implements TransformerInterface
{
 // ...
}

and I changed the signature in the command to inject the interface instead of the concrete class:

class ConsumeBudgetsCommand extends Command {
    public function __construct(FKafka $kafkaClient, TransformerInterface $transformer)
    {
        $this->kafkaClient = $kafkaClient;
        $this->transformer = $transformer;

        parent::__construct();
    }
}

But I get the following issue when I try to run some artisan command

In Container.php line 933: Target [App\Transformers\TransformerInterface] is not instantiable while building [App\Console\Commands\ConsumeBudgetsCommand].

I run previously the issue the following artisan command just in case... cache:clear, clear-compiled, optimize and so on but no luck

What I'm doing wrong? Should I bind the BudgetTransformer in a different way I'm doing now for passing and Interface instead of a concrete class?

Thanks in advance for your help

Update

As @davit suggested, I added

$this->app->bind(TransformerInterface::class, BudgetsTransformer::class);

in AppServiceProvider::register() and I removed

$this->app->bind('budget.transformer', function ($app) {
        return new BudgetsTransformer();
});

there, then I update in AppServiceProvider::boot() the command binding:

$this->app->bind('consume:budgets', function ($app) {
        return new ConsumeBudgetsCommand($app['kafka.client'], $app[TransformerInterface::class]);
});

But still not working, anyway this approach (even working) will not resolve the issue since when I want to add another different transformer implementation, let's say CostTransformer which implements TransformerInterface is gonna always inject BudgetTransformer. So reading the documentation in the link, I found that Contextual Binding could be the solution, so I sustituded by:

$this->app
        ->when(ConsumeBudgetsCommand::class)
        ->needs(TransformerInterface::class)
        ->give(function ($app) {
            return new BudgetsTransformer();
        });

So in that way, I will be able to inject different implementations of transformers to different commands by injecting the interface. But still not working.

Can someone tell me how exactly declare the command binding

$this->app->bind('consume:budgets', function ($app) {
        return new ConsumeBudgetsCommand($app['kafka.client'], ???);
    });

to use that Contextual binding?

回答1:

For binding interfaces must be use this structure https://laravel.com/docs/5.5/container#binding-interfaces-to-implementations

$this->app->bind(TransformerInterface::class, BudgetsTransformer::class);

And

$this->app->bind('consume:budgets', function ($app) {
    return new ConsumeBudgetsCommand($app['kafka.client'], $app->make(TransformerInterface::class));
});