Zend Framework 2 - Applications / Modules / Service Managers - Oh My
Asked Answered
M

2

6

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.

Mardis answered 14/2, 2013 at 19:23 Comment(0)
A
6

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.

Atop answered 15/2, 2013 at 10:5 Comment(9)
I greatly appreciate the writeup (and quickly glanced through the link you provided, I will go through it in depth a little later today). Due to the struggle I was having (partially noted in the question post above), I have also started to read through ZF2 source code to get a better understanding of the workflow. Your response is very well explained coupled with reading the source code, I am starting to get a much better grasp on how the pieces fit together. I am looking forward to the read (link) you provided as at a quick glance it looks like exactly what I need to move from ZF1 to ZF2!Mardis
Given how new the release of ZF2, along with the amount of change in process to a service managed / event driven style, there is huge amount of obscurity in the information available so far (at least from what I have come across), I very much appreciate the link you provided as it seems to describe the transition well.Mardis
@AaronMurray master ServiceManager and EventManager and everything will look much much easier.Atop
"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." - More on this topic, essentially that was what I was meaning by Self-Contained - few dependencies or none at all, so anyone can drop the module into their own application and utilize its functionality within their own app.Mardis
Correct, but don't confuse a module with a container. A module is not a container of <stuff>. It provides shared functionality/behavior.Atop
In This Video Matthew Weier O'Phinney defines a module as: A solution to a problem (singular) - such as a contact form - allows an end user contact a site / blog owner for example. That contact form is made up of the form that is displayed to the end user, the code when submitted to process (email, store in database, etc) the form submission, and the response to the end user that the form was successfully submitted. That's what I am basing my 'definition' of module on. That being a very simple example, 'problems' can be much more in depth.Mardis
Yeah, a module doesn't really have a definition except for the namespace and the Module class itself. That is because you can find modules like github.com/EvanDotPro/EdpSuperluminal (single class) or github.com/doctrine/DoctrineORMModule and other very weird things :) I prefer to define it "layered functionality" because that's the nearest match to what is (usually) providedAtop
let us continue this discussion in chatMardis
A module can be anything, a library, a view script, a whole standalone app, anything.Vereeniging
V
3

I think, particularly in the case of logging, there is a maybe even better, certainly more encapsulated way than using the ServiceManager. ZF2 is essentially an event driven framework and the functionality which enables this event driven architecture can be used to our advantage. Logging is a perfect example for that. Instead of defining a factory, you'd only attach an logger event which can be triggered from anywhere in your application.

Attach a log event listener in your Module.php:

public function onBootstrap(MvcEvent $e)
{
    //setup some $logger

    $sharedManager = $e->getApplication()->getEventManager()->getSharedManager();

    $sharedManager->attach('*', 'log', function($e) use ($logger) {
        /** @var $e MvcEvent */
        $target   = get_class($e->getTarget());
        $message  = $e->getParam('message', 'No message provided');
        $priority = $e->getParam('priority', Logger::INFO);
        $message  = sprintf('%s: %s', $target, $message);
        $logger->log($priority, $message);
    });
}

Then trigger it from anywhere in your application, e.g., a Controller:

$this->getEventManager()->trigger('log', $this, array(
    'priority' => \Zend\Log\Logger::INFO, 
    'message' => 'just some info to be logged'
));
Vereeniging answered 15/2, 2013 at 22:54 Comment(6)
this is not really about having a "logger" available throughout the module, but basically an explanation about how you use the event manager to solve cross-cutting concerns. It's cool, but the inline comment is actually the wanted part :PAtop
I somewhat understand what Markus is saying (through the code), and agree with @Atop in that it is not so much the code that I was asking about it was more understanding how to go from Zend_Frameworks 'resources' to where to now define my 'resources' so they are accessible application wide. I am starting to paint a clearer picture with the information in both answers. I suspect now I will be spending the majority of the weekend getting a strong grasp on ModuleManager, EventManager, and ServiceManager and I think I will be 'Converted'Mardis
I should note, the hardest thing to get past is the logic of Events in a stateless environment, but it is coming to me :)Mardis
It was exactly the purpose of this answer to add another facette to Ocramius' answer because in ZF2 you find much more flexibility in how you do things. Which makes it harder but easier.Vereeniging
I like the concept of log events. That way you just say you need something to be logged, leaving the decision on the application if it should consume the event or not. For the sake of decoupling/unit testing, I'd prefer my model classes to be framework unaware, but then each of the classes must be Logger aware. Kinda stuck there... Or the point is that we only log from controllers?Tarango
@myself: Yes, it looks like Models are supposed to communicate via method results&exceptions, while logging belongs to the application.Tarango

© 2022 - 2024 — McMap. All rights reserved.