Zend Framework 2 Global Database Adapter Object and Config Variables


I wrote this up on Stackoverflow.com too as an answer to my own question. (links below)

So basically , I want some config variables available app-wide in my Zend Framework 2 apps. I also want a database adapter object available and connection. Controllers don’t have preDispatch by default now.  I used to use Zend_Registry in ZF1 to store and retrieve some values and my “db” object. (my database connection and query tool)  I ran into a several problems moving to ZF2.

One problem I ran into was the global variables or config items.  But what really caused me a problem was the lack of an “init” or “preDispatch” method on the Controller.  In addition, I couldn’t add/get objects and values from __construct of the Controller, because that info isn’t yet available in the process.

I also ran into a problem using Zend\Db and how to make my connection available site-wide.  The answer is to use their new ServiceManager in combination with built-in methods to use the ZF2 EventManager.  Not a big deal once setup.

This is how I did it.

1. Adding config variables , site-wide. (and how to access in controllers)

This was easy, once I saw it work. But depends on the preDispatch below.
In your app root, drop a file in config/autoload. Call it:
things.config.local.php

In this file, we just return an array with config items.

 return array( 	
'things' => array(
        'avalue' => 'avalue1',
        'boolvalue' => true,
        ),

);

So I want access to that in my controller. Assuming you create a config property in the controller, you can access it this way. (you setup that property in preDispatch below for it to work) In my system I’d prefer to retrieve that value like so:

$this->config['things']['boolvalue'];

However, you could just call upon it in an action like this:

$config = $this->getServiceLocator()->get('Config');
echo $config['things']['boolvalue'];

That was easy actually. Not sure how to do that with an ini file, but in my case its not needed and my ini files not not a big deal to move into arrays directly. Problem 1 solved for me!

2. How to get preDispatch in controllers (because __construct wont load config)

My other problem was that I could get access to some objects and/or values at a global level AND have them load when the controller and actions are initialized. As I understand it, its not possible to access service manager config in __construct of controller.

$this->getServiceLocator()->get('Config');

The above won’t work. I believe because ServiceManager isn’t available yet during construct of the controller class. makes sense.

A couple extra steps though, and I can get preDispatch working, similar to ZF1. *THEN* the config stuff works. As well as access to global objects, like database.

In the controller add the below method:

protected function attachDefaultListeners()
{
    parent::attachDefaultListeners();
    $events = $this->getEventManager();
    $this->events->attach('dispatch', array($this, 'preDispatch'), 100);
    $this->events->attach('dispatch', array($this, 'postDispatch'), -100);
}

Then add pre and post methods.

 
public function preDispatch (MvcEvent $e)
{
	// this is a db convenience class I setup in global.php
	// under the service_manager factories (will show below)
    $this->db = $this->getServiceLocator()->get('FBDb');
    // this is just standard config loaded from ServiceManager
    // set your property in your class for $config  (protected $config;)
    // then have access in entire controller
    $this->config = $this->getServiceLocator()->get('Config');
    // this comes from the things.config.local.php file
    echo "things boolvalue: " . $this->config['things']['boolvalue'];
}

public function postDispatch (MvcEvent $e)
{
    // Called after actions
}

Problem 2 solved! Init for controlers.

3. How to use the above with ServiceManager to load a global database adapter object for use in Controller

Okay, the last thing I wanted was access to my db globally. And I wanted it controller-wide so I can call $this->db->fetchAll anywhere.

First setup service manager in global.php.
Also, keep in mind, I won’t be leaving it exactly like this as it is in my global.php file. But it works for now.
Add these array’s to the return array in global.php:

'db' => array(
    'driver' => 'Pdo',
    'dsn'   => 'mysql:dbname=mydb;host=localhost;',
    'username' => 'root',
    'password' => '',
    'driver_options' => array(
        PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'UTF8\''
    ),

),
'service_manager' => array(
    'factories' => array(
        'Zend\Db\Adapter\Adapter'
                => 'Zend\Db\Adapter\AdapterServiceFactory',
        'db-adapter' => function($sm) {
                $config = $sm->get('config');
                $config = $config['db'];
                $dbAdapter = new Zend\Db\Adapter\Adapter($config);
                return $dbAdapter;
             },
        'FBDb'  => function($sm) {
                $dba= $sm->get('db-adapter');
                $db = new FBDb\Db($dba);
                return $db;
             },

    ),
),

In the above, I setup the db config, then service_manager has some factories, which are made available when needed in rest of app. In my case, I wanted some convenience for backwards compatibility with some of my old ZF1 code, so I added a custom module called FBDb. I found a wrapper class called ZFBridge by Fabrizio Balliano. Worked great for my needs, you can find here:
https://github.com/fballiano/zfbridge

I took that, modified it a bit, and made a module. So the FBDb object is available in my controllers as my database connection. Same with “db-adapter” if I wanted to utilize it elsewhere.

Anyway, in my controller, I setup “protected $db;” at the start of the class so I have that property available. Then as shown in #2 above , I have preDispatch assigning the FBDb database object to $this->db.

$this->db = $this->getServiceLocator()->get('FBDb');

Then in my action methods, if I want, I can call a record set or db value with this:

$sql = 'select * from customer where cust_nbr between ? and ?';
$rs = $this->db->fetchResults($sql, array('120400', '125250'));

OR, for an array returned:

$rs = $this->db->fetchAll($sql, array('120400', '125250'));

(I added fetchResults to ZFBridge to return only the PDO\Results object form the db query for use in foreach later.)

I know some of this is prbably bad design or “bad OOP”, but it works for me and I like it. I personally don’t lke using pure object based data entities for everything, just some things. Much of the time, I just want to drop a resultset in and be done with it. :)

My original Stackoverflow.com questions and solutions:

http://stackoverflow.com/questions/14128085/zend-framework-2-module-share-variables-between-controllers-onbootstrap/14145872#14145872

http://stackoverflow.com/questions/14107346/zend-framework-2-db-adapter-adapter-query-resultset-like-zf1/14118823#14118823

 

Pastebin of the codes above:  http://pastebin.com/embed_iframe.php?i=33tV7S9Z