How to set up and inject multiple PDO database con

2020-04-11 03:44发布

问题:

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?

回答1:

You have multiple options:

  1. The connection proxy
  2. Extending PDO
  3. Autowired objects

1. The connection proxy

Example:

use PDO;

class ConnectionProxy
{
    private $pdo;

    private $pdo2;

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

    public function getPdo(): PDO
    {
        return $this->pdo;
    }

    public function getPdo2(): PDO
    {
        return $this->pdo2;
    }
}

The container definition:

return [
    ConnectionProxy::class => function (ContainerInterface $c) {
        return new ConnectionProxy(
            $container->get('db1'),
            $container->get('db2')
        );
    },
    'db1' => function (ContainerInterface $container) {
        return new PDO(...);
    },
    'db2' => function (ContainerInterface $container) {
        return new PDO(...);
    },
];

Usage

class MyRepository
{
    private $pdo;

    private $pdo2;

    public function __construct(ConnectionProxy $connectionProxy)
    {
        $this->pdo = $connectionProxy->getPdo();
        $this->pdo2 = $connectionProxy->getPdo2();
    }
}

2. Extending PDO

use PDO;

class PDO2 extends PDO
{

}

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

3. Autowired objects

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



回答2:

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();
    },
];