Zend Framework 2 module share variables between co

2019-03-16 15:25发布

问题:

Is it possible to create variables or even shared objects (like DB adapter) at in Module.php for use in all view controllers? (Zend Framework 2)

For example:

class Module 
{
    public function onBootstrap(MvcEvent $e)
    {

        $moduleConfig = $e->getServiceManager()->get('Config')
    }
}

And in a controller, somehow access using:

$this->moduleConfig['db']

In above I know that would just be the config array for the db adapter, but how would that even work?

I see that one can do this in a controller action:

$config = $this->getServiceLocator()->get('Config')
$dba = new DbAdapter($config['db']);

I don't want to do that every place I need my config. How could I make it something like: $this->config ?? So its available to all actions. Even better, how to do that in the entire Module? I know I am going about this all wrong, but in ZF1 we had Zend_Registry::get() for simple things like this. I am reading over the docs and all sorts of tuts out there, but always there's some assumptions being made and I just get lost. So all I really want is : A) A Globally accessible config item, B) A globally accessible db adapter

I just want to call on $this->db or $this->config->item in my controllers. Is this possible? I know I am missing something totally simple.

Also I did try setting $this->config in my __construct using $this->getServiceLocator()->get('Config'); But I understand that service locator is not available yet during construct. I tried doing similar by setting up a preDispatch in the onBootstrap of the module, but I can't seem to get those items into my controllers.

FYI, I do have this in my global.php (and no, its not staying there, just example):

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

    ),

I am able to access that and other items in any action using getServiceLocator(). I'd like those items to be available as my controller properties as in $this->config->item.

I appreciate any guidance! Thanks!

回答1:

Okay... multipart answer.

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.

<?php
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 controllers.

3. How to use the above with ServiceManager to load a DB 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 fantastic wrapper/bridge-to-zf1-style-db 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. :)

Problems solved. For now. Makes ZF2 much more friendly now, if you are used to ZF1.