4

I could make an instance of PDO and inject it successfully. I defined the PDO::class directly and injected it in the constructor with __construct(PDO $pdo). I would need something like PDO1::class and PDO2::class to inject it like follows: __construct(PDO1 $pdo1, PDO2 $pdo2) but that obviously doesn't work. There is only one PDO class and what I need to do is 2 instances of it with different database credentials.
What is the best way to do it?

I set up one definition of a database via PDO like this and it works:

File: dependencies.php

use DI\ContainerBuilder;
use Psr\Container\ContainerInterface;

return function (ContainerBuilder $containerBuilder) {
    $containerBuilder->addDefinitions([
        PDO::class => function (ContainerInterface $c) {
            $dbSettings = $c->get('settings')['db1'];
            $dsn = 'mysql:host=' . $dbSettings['host'] . ';dbname=' . $dbSettings['dbname'];
            $options = [
                PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
                PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
                PDO::ATTR_EMULATE_PREPARES => false,
            ];
            return new PDO($dsn, $dbSettings['user'], $dbSettings['pass'], $options);
        },
    ]);
};

File: index.php

...
// Set up dependencies
$dependencies = require __DIR__ . '/../app/dependencies.php';
$dependencies($containerBuilder);
// Build PHP-DI Container instance
$container = $containerBuilder->build();
// Set container to create App with on AppFactory
AppFactory::setContainer($container);
// Instantiate the app
$app = AppFactory::create();
...

File SomeRepository.php

use PDO;

class SomeRepository{

    protected $pdo;

    public function __construct(PDO $pdo) {
        $this->pdo = $pdo;
    }
}

I've seen something like this in this article:

return function (ContainerBuilder $containerBuilder) {
    $containerBuilder->addDefinitions([
        'db1' => function (ContainerInterface $c) {
            $db1Settings = $c->get('settings')['db1'];
            $dsn = 'mysql:host=' . $db1Settings['host'] . ';dbname=' . $db1Settings['dbname'];
            $options = [ ... ];
            return new PDO($dsn, $db1Settings['user'], $db1Settings['pass'],$options);
        },
        'db2' => function (ContainerInterface $c) {
            $db2Settings = $c->get('settings')['db2'];
            $dsn = 'mysql:host=' . $db2Settings['host'] . ';dbname=' . $db2Settings['dbname'];
            $options = [ ... ];
            return new PDO($dsn, $db2Settings['user'], $db2Settings['pass'],$options);
        },

    ]);
};

But is it the best way to do it? And how can I access the connections in a repository class without having to inject the whole container?

Samuel Gfeller
  • 840
  • 9
  • 19
  • Whats wrong with `__construct(PDO $pdo1, PDO $pdo2)` remember the `PDO` is a type hint, so hint that all the params are PDO – RiggsFolly Sep 02 '19 at 13:48

2 Answers2

6

You have multiple options:

  1. Extending PDO
  2. Autowired objects

1. Extending PDO

use PDO;

class PDO2 extends PDO
{
    // must be empty
}

The container definition:

use PDO2;

// ...

return [
    PDO::class => function (ContainerInterface $container) {
        return new PDO(...);
    },

    PDO2::class => function (ContainerInterface $container) {
        return new PDO2(...);
    },
];

Usage

use PDO;
use PDO2;

class MyRepository
{
    private $pdo;

    private $pdo2;
    
    public function __construct(PDO $pdo, PDO2 $pdo2)
    {
        $this->pdo = $pdo;
        $this->pdo2 = $pdo2;
    }
}

2. Autowired objects

See Matthieu Napoli's answer: https://stackoverflow.com/a/57758106/1461181

odan
  • 4,757
  • 5
  • 20
  • 49
4

If you have multiple instances of a class in your app (here you have multiple instances of the PDO class), then you must configure which one to inject every time.

That means that PDO cannot be autowired, because PHP-DI cannot decide which instance you want depending on the service/controller/etc.

You need to use configuration (see http://php-di.org/doc/php-definitions.html#autowired-objects) to define which instance (db1 or db2 in your example) to inject for each service.

return [
    MyService::class => DI\autowire()
        ->constructorParameter('pdo', DI\get('db1'))
        ->constructorParameter('pdo2', DI\get('db2')),

    'db1' => function (ContainerInterface $c) {
        return new PDO();
    },
    'db2' => function (ContainerInterface $c) {
        return new PDO();
    },
];
Matthieu Napoli
  • 48,448
  • 45
  • 173
  • 261
  • Thank you for your answer! Would you mind adding an example (definition and usage in service) which matches my code because I struggle a bit to understand what autowiring is and how I could implement it in my example? – Samuel Gfeller Sep 02 '19 at 14:43
  • 1
    @SamuelGfeller I have added an example. – Matthieu Napoli Sep 03 '19 at 15:03