6

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.

simPod
  • 11,498
  • 17
  • 86
  • 139
  • 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. – Cerad Sep 15 '17 at 12:37

5 Answers5

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) {...}
}
Guzik
  • 194
  • 6
  • This is much simplier must admit – simPod Nov 24 '17 at 13:13
  • 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.` – BrianV Sep 18 '20 at 20:15
  • 1
    If you're receiving the above error reported by BrianV you need to make sure your "Connection" directory is not autowired – Pez Jan 15 '21 at 23:09
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.

Arkowsky
  • 851
  • 7
  • 19
  • 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. – simPod Sep 17 '17 at 14:34
  • I have edited my answer, and created solution which autowire connections(you don't have to inject them every time). – Arkowsky Oct 02 '17 at 20:46
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)
  • 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. – simPod Nov 05 '18 at 12:08
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

Michał Szczech
  • 466
  • 4
  • 17
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?

123pierre
  • 305
  • 1
  • 4
  • 18
  • Sorry but I don't see there anything that would help here – simPod Mar 10 '18 at 14:25
  • 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.. – 123pierre Mar 10 '18 at 19:10
  • 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. – simPod Mar 10 '18 at 22:30