1

In Laravel documentation I see example:

$this->app->when(PhotoController::class)
          ->needs(Filesystem::class)
          ->give(function () {
              return Storage::disk('local');
          });

$this->app->when([VideoController::class, UploadController::class])
          ->needs(Filesystem::class)
          ->give(function () {
              return Storage::disk('s3');
          });

https://laravel.com/docs/8.x/container#contextual-binding

I want make the same with diffenet Log channels insead Storage disks.

https://laravel.com/docs/8.x/logging#writing-to-specific-channels

I try:

    public function __construct(LoggerInterface $logger) {
        $this->logger = $logger;
    }
$this->app->when(PhotoController::class)
          ->needs('log')
          ->give(function () {
              return \Log::channel('telegram');
          });

$this->app->when([VideoController::class, UploadController::class])
          ->needs('log')
          ->give(function () {
              return \Log::channel('slack');
          });

But I get error:

NOTICE: PHP message: PHP Fatal error:  Uncaught Error: Maximum function nesting level of '256' reached, aborting! in /var/www/html/vendor/laravel/framework/src/Illuminate/Foundation/Application.php:792
 Stack trace:
 #0 /var/www/html/vendor/laravel/framework/src/Illuminate/Container/Container.php(646): Illuminate\Foundation\Application->resolve('Illuminate\\Log\\...')
 #1 /var/www/html/app/Providers/AppServiceProvider.php(106): Illuminate\Container\Container->get('Illuminate\\Log\\...')
 #2 /var/www/html/vendor/laravel/framework/src/Illuminate/Container/Container.php(805): App\Providers\AppServiceProvider->App\Providers\{closure}(Object(Illuminate\Foundation\Application), Array)
 #3 /var/www/html/vendor/laravel/framework/src/Illuminate/Container/Container.php(691): Illuminate\Container\Container->build(Object(Closure))
 #4 /var/www/html/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(796): Illuminate\Container\Container->resolve('log', Array, true)
 #5 /var/www/html/vendor/laravel/framework/src/Illuminate/Container/Container.php(646):  in /var/www/html/vendor/laravel/framework/src/Illuminate/Foundation/Application.php on line 792

Also I tried:

->needs(LoggerInterface::class)

And the same error.

I do not see examples of how to do this correctly in the documentation. There is nothing about it.

4n70wa
  • 337
  • 4
  • 19
  • I believe this is because you cannot inject an interface, but instead it has to be an implementation, as per example with storage you get a concrete instance with particular storage type, while with log it's an interface that system cannot resolve. – Daniel Protopopov Mar 30 '21 at 14:32
  • A recursive loop occurs on the line `return \Log::channel ('slack');` because it calls the container to resolve the Log for the Log. Need some workaround. – 4n70wa Mar 30 '21 at 14:37
  • Perhaps try [binding](https://jinoantony.com/blog/code-to-interface-an-example-in-laravel) your interface first? – Daniel Protopopov Mar 30 '21 at 14:50

1 Answers1

0

Sidenote: you can bind to an interface, its a common usage, Laravel calls them 'Contracts', see https://laravel.com/docs/8.x/container#binding-interfaces-to-implementations . So that is not the issue here.

Onto the question: I can't quite figure out what I'm doing differently but it works as intended for me:

use Illuminate\Support\Facades\Log;
$this->app->when(SomeController::class)->needs('log')->give(function() {
    return Log::channel('mychannel');
});

However, the 'log' string I dont quite like here. I believe it refers to the name of the so called "facade" for the logger, which brings more cognitive overload to what exactly this refers to. Imho its better to use a full class name here, to be more explicit:

use Illuminate\Log\Logger;
use Illuminate\Support\Facades\Log;
$this->app->when(SomeController::class)->needs(Logger::class)->give(function() {
    return Log::channel('mychannel');
});

You can then use the following snippet in any of your controllers:

public function __construct(Logger $logger) {
    $this->logger = $logger;
}

In fact you have a couple of options of defining the when/needs/give:

  • Illuminate\Log\Logger::class
  • Psr\Log\LoggerInterface::class (Logger above implements this)

Both work, but in both cases you need to use the exact class in your (to be injected) constructor exactly as written. You cannot bind the LoggerInterface and typehint Logger in your controller constructor and expect it to work, it does not recognize inheritance in this way apparently.

Another nice thing to know, is that apparently this only works on constructors directly. Controller methods have dependency injection as well but this does not work in combination with the contextual binding. However, it works with the default "simple" bindings as defined here https://laravel.com/docs/7.x/container#binding :

public function showUser(Illuminate\Log\Logger $log) {
    // This typehint does not take contextual binding into account. You will receive
    // the default binding for this class.
    $log->info('...');
}

See https://github.com/laravel/framework/issues/6177 (tl;dr: the issue is closed and contextual bindings for controller methods will not be supported in the future).

I hope this information helps you out.

Flame
  • 6,663
  • 3
  • 33
  • 53
  • It's work if use `Illuminate\Log\Logger::class` but don't work if use `Psr\Log\LoggerInterface::class`. It’s strange. – 4n70wa Mar 31 '21 at 13:54
  • if you are using that same class in the typehint then I dont know why it doesnt work – Flame Mar 31 '21 at 13:57