zend-framework2zend-dbzend-log

Zend Framework 2 - Applications / Modules / Service Managers - Oh My


I have just started learning Zend Framework 2 as a long time Zend Framework 1 developer. I am having a little trouble wrapping my head around the new terminology.

Back in ZF1, if I wanted to create a logger that was global to an application I would add the configuration into the application.ini file and the bootstrap would initialize it as a resource (I hope I am saying that right). So then from any of my module controllers I could access the logger through bootstrap resources.

Enter ZF2, Modules are a bit different beast, they are self contained, but I am a bit confused about how they interact with the application. It seems to me like this is where the ServiceManager comes into play. My goal is, to have my Module (not the controller, but the module itself), to check if the Application has defined a logger and if it has, utilize that logger throughout the module. If the application does not define a logger, I want the module to define the logger for module wide logging.

This question also relates to databases as well, let's say I want to have the application define the logic of the database connection, while I want the module to define the logic of the tables it requires. How exactly do I configure this, and how/where can I tell if there is already a database resource defined in the Application.

Note: I have gone through Rob Allen's Quickstart (quite information and the only resource I have found that lacks obscurity thusfar), and the ZF2 (readthedocs), and googled tons already. What I am finding is that the information is generally very obscure when it comes to 'where' certain pieces of the puzzle go.


Solution

  • What you know from Zend Framework 1.x is an "Application Resource".

    The concept of "application resource" is replaced in Zend Framework 2 by so-called "services" (intro here)

    An other change is modules themselves. In ZF1, a module was mainly a sub-section of your application that handled some requests. This is no longer true in ZF2: if your module defines a service or controller, that one is now accessible to all the application. There's a nice intro on some differences between ZF1 and ZF2 by Gary Hockin.

    But anyway, modules are NOT self-contained. They should be developed in insulated environment and with as little dependencies as possible, but they provide cross-concerns functionality that affects all of your application.

    For your specific case of the logger, I suggest that your module always defines a logger and consumes it. What can be done to define the logger conditionally is following:

    class MyModule
    {
        public function onBootstrap($e)
        {
            // $e->getTarget() is the \Zend\Mvc\Application
            $sm = $e->getTarget()->getServiceManager();
    
            if (!$sm->has('some-logger-name')) {
                $sm->setFactory('some-logger-name', function ($sl) {
                    return new MyLogger($sl->get('some-db'));
                });
            }
        }
    }
    

    You would then be able to use your 'some-logger-name' across all your application.

    A different approach is to just define the logger services and let other modules or configurations override it later on:

    class MyModule
    {
        public function getConfig()
        {
            return array(
                'service_manager' => array(
                    'factories' => array(
                        'some-logger-name' => 'My\Logger\Factory\ClassName'
                    ),
                ),
            );
        }
    }
    

    Same is achieved with getServiceConfig, which is less flexible and cannot be cached, but has higher priority over getConfig (allows overrides) and lets you also define service factories as closures:

    class MyModule
    {
        public function getServiceConfig()
        {
            return array(
                'factories' => array(
                    'some-logger-name' => function ($sl) {
                        return new MyLogger($sl->get('some-db'));
                    },
                ),
            );
        }
    }
    

    You could then even define a config key that has to be used to decide which logger (service name) to use.

    The concept with modules and configurations is that "last module wins", so you may define the service 'some-logger-name' either in your module or in any module loaded before it.

    Same concepts apply also to your DB connection.

    As you can see, moving to services already gave you a certain degree of freedom.

    Keep in mind that it is not that the "Application" defines something for you: modules define your services/configs/events etc... The running application is then a composition of all these things together.