zf2 doctrine2 and Zend\\Db\\Adapter\\Adapter using

2019-03-17 00:21发布

问题:

I use doctrine2 with ZF2, some of my libraries work with Zend\Db\Adapter\Adapter, others with doctrine2. Now, they connect to database twice. Is it possible to use one db connection in doctrine and standard ZF2 db adapter?

回答1:

The DoctrineORM module accepts a PDO resource or a service name where the instance can be located in the service manager instead of the usual connection params.

First step is to create a service factory which retrieves the PDO resource from the Zend\Db\Adapter\Adapter service

<?php
namespace Application\Db\Service;

use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
use Zend\ServiceManager\Exception\ServiceNotCreatedException;

class PdoResourceFactory implements FactoryInterface
{
    /**
     * @param ServiceLocatorInterface $serviceLocator
     * @return \PDO resource
     */
    public function createService(ServiceLocatorInterface $services)
    {
        $dbAdapter = $services->get('Zend\Db\Adapter\Adapter');

        $pdo = $dbAdapter->getDriver()->getConnection()->getResource();
        if (!$pdo instanceof \PDO) {
            throw new ServiceNotCreatedException('Connection resource must be an instance of PDO');
        }
        return $pdo;        
    }
} 

Once you have the factory, it's just a case of adding it to the service manager, configuring the db params for Zend\Db\Adapter\Adapter and telling doctrine to use the existing PdoResource from the service manager to connect.

Assuming you did this all in one file, let's say dbconn.local.php...

<?php
return array (
    'service_manager' => array(
        'factories' => array(
            'Zend\Db\Adapter\Adapter' => 'Zend\Db\Adapter\AdapterServiceFactory',
            // include the pdo resource factory
            'PdoResource' => 'Application\Db\Service\PdoResourceFactory',
        ),
    ),
    // db adapter config
    'db' => array(
        'driver'    => 'pdo',
        'dsn'       => 'mysql:dbname=database;host=127.0.0.1',
        'username'  => 'username',
        'password'  => 'password',
    ),

    'doctrine' => array (
        'connection' => array (
            'orm_default' => array (
                'driverClass' => 'Doctrine\DBAL\Driver\PDOMySql\Driver',
                // use the resource from the zend adapter 
                'pdo' => 'PdoResource',
            ),
        ),
    ),
);


回答2:

Sorry for posting this as new answer but I am not able to add a comment to Crisp's answer since my reputation is too low because I only registered to stackoverflow for writing this comment:

In the dbconn.local.php that Crisp posted be sure to set dbname to null like in the following snippet:

Addition to Crisp's answer:

<?php
return array(
    'service_manager' => array(
        'factories' => array(
            'Zend\Db\Adapter\Adapter' => 'Zend\Db\Adapter\AdapterServiceFactory',
            // the lazy way of Crisp's PdoResourceFactory:
            'PdoResource' => function (ServiceLocatorInterface $services) {
                $dbAdapter = $services->get('Zend\Db\Adapter\Adapter');

                $pdo = $dbAdapter->getDriver()->getConnection()->getResource();
                if (!$pdo instanceof \PDO) {
                    throw new ServiceNotCreatedException('Connection resource must be an instance of PDO');
                }
                return $pdo;  
            },
        ),
    ),
    // db adapter config
    'db' => array(
        'driver'    => 'pdo',
        'dsn'       => 'mysql:dbname=database;host=127.0.0.1',
        'username'  => 'username',
        'password'  => 'password',
    ),

    'doctrine' => array (
        'connection' => array (
            'orm_default' => array (
                'driverClass' => 'Doctrine\DBAL\Driver\PDOMySql\Driver',
                // use the resource from the zend adapter 
                'pdo' => 'PdoResource',
                // important addition to Crisp's answer:
                'params' => array(
                    'dbname' => null,
                ),
            ),
        ),
    ),
);

And now here is why this is important:

When calling

$em->getConnection()->getDatabase();

on your EntityManager without having set the dbname to null you will get "database" as the name of your database because this is the default value which is set by the module.config.php of the DoctrineORMModule as you can see here. Setting the dbname to null will cause your Doctrine\DBAL\Driver\PDOMySql\Driver which extends Doctrine\DBAL\Driver\AbstractMySQLDriver to load the name of the database via SELECT DATABASE() from the database itself as you can see here.

Also not setting the dbname to null (or to the correct database name) will cause the schemaInSyncWithMetadata() function of the Doctrine\ORM\Tools\SchemaValidator to always return false since it cannot load the current database setup because it uses the Doctrine\ORM\Tools\SchemaTool which uses the EntityManager's Connection which thinks that the database being used is called "database".

So I hope someone can use this information to save some time. I wasted half the day to figure that out.

And many thanks to Crisp again for his answer that saved me a lot of time.