Search code examples
phplaravellaravel-artisanservice-provider

Laravel Target is not instantiable while building


I created an Artisan command which worked and where I injected a Kafka client service as the first parameter and a concrete class BudgetsTransformer, as the second 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?

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 substituted 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?


Solution

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