0

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?

random
  • 9,774
  • 10
  • 66
  • 83
Averias
  • 931
  • 1
  • 11
  • 20
  • 1
    You don't need any of the `$this->app->bind(...` because your app doesn't use any of them to create the `ConsumeBudgetsCommand` object. What you only need is to bind the `TransformerInterface` to the `BudgetTransformer`, so that the app would know to inject it then executing the command. – thefallen Jun 19 '18 at 11:54

1 Answers1

5

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));
});
Davit Zeynalyan
  • 8,418
  • 5
  • 30
  • 55
  • @Averias are you try this?? – Davit Zeynalyan Jun 19 '18 at 12:30
  • yes @Davit, I tried, please read my update on the question above – Averias Jun 19 '18 at 13:03
  • The same as before: Target [App\Transformers\TransformerInterface] is not instantiable while building [App\Console\Commands\ConsumeBudgetsCommand]. – Averias Jun 19 '18 at 13:17
  • Why you try to bind?? laravel must be automatically bind it. Are you use in command **protected $signature = 'consume:budgets'**??; – Davit Zeynalyan Jun 19 '18 at 13:20
  • I want to bind the command since I'm injecting the Kafka service (as 1st param in the command). Anyway even removing the bind for the command: $this->app->bind('consume:budgets',.... and also the command declaration: $this->commands('consume:budgets'); it doesn't work(same error), and to be honest I would like to know how to use both bindings cause it can be a use case that I will face in the near future – Averias Jun 19 '18 at 13:24
  • Still the same issue, even when I just used: $this->app->bind(TransformerInterface::class, BudgetsTransformer::class); instead of Contextual Binding, the only way that works is when I come back and pass a concrete implementation of the transformer as the second param in the command – Averias Jun 19 '18 at 13:40
  • Are you try this **$app->make(TransformerInterface::class)**? for binding **'consume:budgets'** – Davit Zeynalyan Jun 19 '18 at 13:41
  • I tried **$this->app->bind('some-method', function ($app) {dd($app->make(SomeInterface::class))** and it is instance of SomeInterface::class implemention class. try it and show answer – Davit Zeynalyan Jun 19 '18 at 13:52
  • I tried as u said, by adding the dd() function but still same issue: "Target [App\Transformers\TransformerInterface] is not instantiable while building [App\Console\Commands\ConsumeBudgetsCommand]." So I'm thinking that probably the issue is coming from another part, I will review the code. Thank u so much for your effort and time – Averias Jun 19 '18 at 14:01
  • Wooow finally it was an issue in the interface definition, sorry, I drove you crazy @Davit, anyway I checked your answer out and it worked properly, then I changed it by contextual binding and it worked properly. Thanks for your time and answers! – Averias Jun 19 '18 at 14:52