Autowire specific DBAL connection when using multiple of them
Asked Answered
B

6

7

I'm using Doctrine 2 where I have multiple connections for DBAL. I have also multiple EntityManagers in ORM.

I need to be able to somehow autowire specific DBAL connection into other Symfony 3 services.

I can autowire any EntitiyManager using EntityManagerDecorator but don't know how to do the same with connection. I'm able to get the connection from EntityManager but I don't think it's the way to go.

Birkle answered 15/9, 2017 at 8:43 Comment(1)
Lots of people down vote when they don't understand the question. I'd be interested in seeing how you were using a decorator for entity managers. But as far as the connection goes, I think you will have to define the service and specify the connection. But at least the other dependencies will still be autowired.Serpens
D
10

You can specify wrapper class for doctrine connections, no proxy class needed:

#config.yml
doctrine:
    dbal:
        connections:
            default:
                wrapper_class: AppBundle\Connections\ConnectionDefault
                ...
            second:
                wrapper_class: AppBundle\Connections\ConnectionSecond
                ...

Connections should extend Doctrine\DBAL\Connection:

<?php

namespace AppBundle\Connection;

use Doctrine\DBAL\Connection;

class ConnectionDefault extends Connection
{

}

class ConnectionSecond extends Connection
{

}

and create service aliases:

#services.yml
services:
    ...
    AppBundle\Connections\ConnectionDefault: '@doctrine.dbal.default_connection'
    AppBundle\Connections\ConnectionSecond: '@doctrine.dbal.second_connection'

then you can simply inject desired connection into service by typehinting it:

class MyService {
    public function __construct(ConnectionDefault $connection) {...}
}

class MyOtherService {
    public function __construct(ConnectionSecond $connection) {...}
}
Downstage answered 26/10, 2017 at 11:29 Comment(3)
This is much simplier must admitBirkle
This does not work in current versions of Symfony. It errors out with the following: Cannot autowire service "App\Connection\ConnectionDefault": argument "$params" of method "Doctrine\DBAL\Connection::__construct()" is type-hinted "array", you should configure its value explicitly.Bundle
If you're receiving the above error reported by BrianV you need to make sure your "Connection" directory is not autowiredSuperscribe
L
4

I had the same issue and tested it more precisely what works and why.

WORK FOR MANY DBAL CONNECTIONS USING PROXY

EDIT: I have done some proxy abstract class. Now each my connection class inherited from this proxy. It works fine :)

<?php

#src/AppBundle/Connections/ConnectionProxy.php

namespace AppBundle\Connections;

use Doctrine\DBAL\Connection;

abstract class ConnectionProxy
{
    private $connection;

    public function __construct(Connection $connection)
    {
        $this->connection = $connection;
    }

    public function __call($method, $arguments)
    {
        if (is_callable(array($this->connection, $method))) {
            return call_user_func_array(array($this->connection, $method), $arguments);
        } else {
            return new \Exception("Call to undefined method '{$method}'");
        }
    }
}

My connection default class:

<?php

#src/AppBundle/Connections/ConnectionDefault.php

namespace AppBundle\Connections;

class ConnectionDefault extends ConnectionProxy
{

}

second connection:

<?php

namespace AppBundle\Connections;

class ConnectionSecond extends ConnectionProxy
{

}

Then I have added manually to my services file, for all my connections(2 connections) :

AppBundle\Connections\ConnectionDefault:
    public: true
    class: AppBundle\Connections\ConnectionDefault
    arguments: ['@doctrine.dbal.default_connection']

AppBundle\Connections\ConnectionSecond:
    public: true
    class: AppBundle\Connections\ConnectionSecond
    arguments: ['@doctrine.dbal.second_connection']

Then my connections are automatically autowiring when I use their types

class SaveEvent
{
    /** @var  Connection */
    private $connection;

    public function __construct(ConnectionDefault $connection)
    {
        $this->connection = $connection;
    }
    .....

INJECT CONNECTIONS FOR EACH SERVICE

The simplest option - but require create each service separately and inject connection name - it means manually wiring Arguments** in your services file(app/config/services.yml) e.g.

AppBundle\Classes\SaveEvent:
    public: true
    arguments:
      - '@doctrine.dbal.[connection_name]_connection'

where connection_name is your connection name. In below example we have two connections in config.yml and this value could be “default” or “database2”:

doctrine:
    dbal:
        default_connection: default
        connections:
            default:
              driver: pdo_sqlite
              charset: UTF8
              path: '%database_path%'
            database2:
              driver: pdo_sqlite
              charset: UTF8
              path: '%database_path%'

WORK FOR ONLY ONE DBAL CONNECTION

Autowiring for DBAL Connections works fine when we have only one connection. If I leave only default connection(remove connection database 2 from config.yml) and add some path to be autowired in app/config/services.yml:

AppBundle\Classes\:
    resource: '../../src/AppBundle/Classes'
    public: true

and if I use as type class Doctrine\DBAL\Connection in my Class, it means that I want inject default connection(@doctrine.dbal.default_connection) because I have the only one connection.

namespace AppBundle\Classes;

use Doctrine\DBAL\Connection;

class SaveEvent
{
    /** @var  Connection */
    private $connection;

    public function __construct(Connection $connection)
    {
        $this->connection = $connection;
    }

// …
}

ANSWER

// Edited SimPod, the answer to your question is WORK FOR MANY DBAL CONNECTIONS USING PROXY or INJECT CONNECTIONS FOR EACH SERVICE as I described above.

I'm aware that you have managed with that issue but my answer can help the others.

Laceylach answered 17/9, 2017 at 14:28 Comment(2)
I think you answer summmarizes it better. I'll mark it as answered as it may help others too. You're right. The Connection autowiring is currently kind of impossible when having multiple of them and manually wiring them is a bit of pain.Birkle
I have edited my answer, and created solution which autowire connections(you don't have to inject them every time).Laceylach
S
4

A simpler solution can be:

#services.yml
services:
    _defaults:
        bind:
            $dbSecond: '@doctrine.dbal.second_connection'

Use in controller or services:

public function my(Request $request, Connection $dbSecond)
Scurlock answered 1/11, 2018 at 20:25 Comment(1)
That even though this might work, the dependency injection should work based on type and not variable name. Basing it on variable name might lead to unexpected and unpleasant surprises for every other developer that works with such a code.Birkle
C
2

Each DBAL connection is always accessible in service container with the following identifier:

doctrine.dbal.[name]_connection

where [name] is the connection name

https://github.com/doctrine/DoctrineBundle/blob/master/Resources/doc/configuration.rst#doctrine-dbal-configuration

Creator answered 15/9, 2017 at 9:5 Comment(0)
K
0

there is a new functionality in version 3.4 which gives the process much easier. See : Symfony 3.3 - Entity Manager injection into services with multiple database?

Kataway answered 9/3, 2018 at 17:8 Comment(3)
Sorry but I don't see there anything that would help hereBirkle
As far as I understand, since symfony 3.4 you can implement default parameters for all your services. Then, you can set a default EM for all services ? But I have to admit that I have quickly read the manual.. maybe i am wrong..Kataway
I don't think it's a good way to go even if that possibility exists (haven't checked the docs myself though). I think you should rather specify your dependencies for each service explicitly within it.Birkle
B
0

In more recent versions of Symfony (which is currently at version 7) there are cleaner built in ways to achieve this.

Both of these require you to configure the connections in the Doctrine configuration file like shown here.

Method 1: autowiring connections

From the Symfony documentation for the Doctrine Bundle:

You can autowire different connections by type-hinting your service arguments with the following syntax: Doctrine\DBAL\Connection $Connection. For example, to inject a connection with the name purchase_logs use this:

// src/Controller/SomeController.php
use Doctrine\DBAL\Connection;

class SomeController
{
    public function __construct(Connection $purchaseLogsConnection)
    {
        $this->connection = $purchaseLogsConnection;
    }
}

Method 2: injecting the ManagerRegistry class

Again, from the Symfony documentation:

In a controller you can access it using the getConnection() method and the name of the connection:

// src/Controller/SomeController.php
use Doctrine\Persistence\ManagerRegistry;

class SomeController
{
    public function someMethod(ManagerRegistry $doctrine): void
    {
        $connection = $doctrine->getConnection('customer');
        $result = $connection->fetchAllAssociative('SELECT name FROM customer');

        // ...
    }
}

This last method, like the previous one, is also available in services, not just in controllers.

Bertram answered 29/4 at 21:19 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.