2

I am currently working on a slim 3 application that is using a php-di bridge to serve dependencies. Everything is working flawlessly within controllers, however, I came across an issue when trying to add dependencies to a model observer. Eloquents capsule manager has its own container so I am unable to pass through dependencies (not injected) without it throwing an error.

Current setup:

$app = new \App\App; // extends \DI\Bridge\Slim\App
$container = $app->getContainer();

$capsule = new Capsule;
$capsule->addConnection($settings['settings']['db']);
$capsule->setEventDispatcher(new Dispatcher());
$capsule->setAsGlobal();
$capsule->bootEloquent();

// Sale model extends eloquent model
Sale::observe(new SaleObserver($container));

SaleObserver Class:

class SaleObserver {

    protected $container;

    public function __construct($container){
        $this->container = $container;
    }
    public function saving(Sale $sale){

        // Mail logic using container

    }

}

This gives me the error triggered from my SaleObserver:

Unresolvable dependency resolving [Parameter #0 [ <required> $container ]]

I think the problem is that the SaleObserver is being resolved my eloquent's own container which is not allowing me to pass my PHP-DI "$container" through. Is something like this possible within slim 3 without a hacky approach?

I am testing with container just to see if I can pass something through, however, my main objective is to simply pass through my mail definition since it has all the configs already setup.

Update:

I have previously tried to type hint within the SaleObserver class like below in hopes that php-di would catch it with no avail.

use App\Mail\Contracts\MailerInterface;

class SaleObserver {

    protected $mail;

    public function __construct(MailerInterface $mail){

        $this->mail = $mail;

    }

    public function updating(Sale $sale){

        // Send Mail logic

    }

}

I end up getting a different error that suggests that php-di auto wiring is not working with classes that are not not connected to a route even though auto wiring is set to true because the injected parameter is not automatically instantiating as it should.

This shows the error:

Uncaught ArgumentCountError: Too few arguments to function

The bridge I am using is PHP-DI/Slim-Bridge

Derek Gutierrez
  • 628
  • 1
  • 6
  • 16
  • Maybe you need to typehint class/interface name, so PHP-DI can autowire them. – Zamrony P. Juhara Jul 26 '18 at 00:47
  • I updated my question to reflect your input, that was actually one of the first things I tried, I don't believe php-di/slim-bridge pays attention to any classes that aren't connected to a route request. – Derek Gutierrez Jul 26 '18 at 16:07

2 Answers2

0

Unfortunately, seems like Laravel's database event handling mechanism heavily relies on Laravel's container implementation. After reading this great post, I managed to make events work as expected.

I set up an instance of \Illuminate\Container\Container and added it to and used it to my application container ($container). This instance of Laravel container is going to be used only for handling events. Now I can set up an instance of \Illuminate\Events\Dispatcher and register it with setting up the Manager instance:

$container['laravel-container'] = function ($c){
    return new \Illuminate\Container\Container;
};

$container['database-event-dispatcher'] = function ($c) {
    return new \Illuminate\Events\Dispatcher($c['laravel-container']);
};

$capsule = new \Illuminate\Database\Capsule\Manager();
$capsule->addConnection($container['settings']['db']);
// Pass event dispatcher instance from application container
$capsule->setEventDispatcher($container['database-event-dispatcher']);
$capsule->setAsGlobal();
// Now that we're using events it is important to call $capsule->bootEloquent();
$capsule->bootEloquent();

A when creating a new instance of the observer, we can pass our applications $container, or only pass the objects we need in the observer.

So, assuming this is our SaleObserver class:

class SaleObserver {

    protected $mail;

    public function __construct(MailerInterface $mail){
        $this->mail = $mail;
    }

    public function updating(Sale $sale){
        // Send Mail logic
    }

}

we should give it the mailer instance:

$container ['mailer'] = function ($c) {
    // set up and return a MailerInterface
};

Sale::observe(new SaleObserver($container['mailer']));
Nima
  • 3,309
  • 6
  • 27
  • 44
0

It's possible to use just the Connection object with an empty Laravel container like this:

use Illuminate\Database\Connection;
use Illuminate\Database\Connectors\ConnectionFactory;
use Psr\Container\ContainerInterface as Container;

$container[Connection::class] = function (Container $container) {
    $settings = $container->get('settings');
    $config = [
        'driver' => 'mysql',
        'host' => $settings['db']['host'],
        'database' => $settings['db']['database'],
        'username' => $settings['db']['username'],
        'password' => $settings['db']['password'],
        'charset' => $settings['db']['charset'],
        'collation' => $settings['db']['collation'],
        'prefix' => '',
    ];

    $factory = new ConnectionFactory(new \Illuminate\Container\Container());
    $connection = $factory->make($config);

    // Disable the query log to prevent memory issues
    $connection->disableQueryLog();

    return $connection;
};
odan
  • 4,757
  • 5
  • 20
  • 49