Zend Framework 2: Abusing ZeDb to manage multiple connections
Tuesday, February 18. 2014
My favorite way of approaching DAL in ZF2 is ZeDb. It uses lot of existing functionality, but adds a nice layer of its own stuff, and that stuff is really good. However, ZeDb has one single flaw, it does not support multiple database connections. The typical thinking is, that who an earth would want that. I do. I want other weird things too, but in an everyday real application, you simply need more connections than one.
I came up with an simple solution by abusing ZF2's ServiceManager. Typically you gain access to a model is via something like this:
$manager = $this->getServiceLocator()->get('ZeDbManager');
$model = $manager->get('Application\Entity\Prices');
To get that working you need to do the config in module.config.php, declare ZeDb's DatabaseManagerFactory and override Zend\Db\Adapter\Adapter with AdapterFactory and finally declare your DB-configuration and list the models. Its all in the ZeDb docs.
My idea exploits all three of those. I'll instantiate multiple DatabaseManagers via ServiceManager. Also, every model will select an existing DB-adapter for itself. To get that working there will be separate configuration for all of the connections.
Example module configuration:
return array(
'service_manager' => array(
'factories' => array(
'ZeDbManager' => 'ZeDb\Service\DatabaseManagerFactory',
'Zend\Db\Adapter\Adapter' => 'ZeDb\Service\AdapterFactory',
)
),
'zedb_db_in' => array(
'adapter' => array(
'driver' => 'pdo_pgsql',
),
'models' => array(
'Application\Model\Int\Products' => array(
'tableName' => 'products',
'entityClass' => 'Application\Entity\Int\Products',
),
),
),
'zedb_db_out' => array(
'adapter' => array(
'driver' => 'pdo_mysql',
),
'models' => array(
'Application\Model\Internal\Customers' => array(
'tableName' => 'customers',
'entityClass' => 'Application\Entity\Internal\Customers',
),
),
),
Here I declare the obligatory parts, and two separate configurations with names zedb_db_in and zedb_db_out.
I'll have to prime both the DatabaseManagers and Adapters in Module's onBootstrap(). After the objects exist, they are made accessible with:
public function getServiceConfig()
{
return array(
'factories' => array(
'zedb_db_in' => function (ServiceLocatorInterface $sm) {
return Module::getInDbManager();
},
'ZeDbAdapter_in' => function (ServiceLocatorInterface $sm) {
return Module::getInDbAdapter();
},
),
);
}
The model has to do some heavy lifting to get rid of the default Adapter:
class Products extends ZeDb\Model
{
public function __construct(Adapter $adapter, $options = null)
{
$adapter = $this->getDatabaseManager()
->getServiceLocator()
->get('ZeDbAdapter_in');
parent::__construct($adapter, $options);
}public function getDatabaseManager()
{
return Module::getInternalDbManager();
}
Now the setup is ready. In an action you can simply do:
$inDbManager = $this->getServiceLocator()->get('zedb_db_in');
$productsModel = $inDbManager->get('Application\Entity\Int\Products');
$products = $productsModel->SearchProductByCode('ABC123', null);
There is no limit on how many DB-connections you can have. Note that, the ZeDb-module bootstrap will initialize the default manager and adapter, but the idea is not to use them at all. Anyway, I'll stick with this until something better comes.