Easy Setter Injection in Zend Framework 2

Zend Framework 2 Forms

Recently I’ve been learning loads, thanks @ocramius, about dependency injection, and how it’s implemented in Zend Framework 2. Despite being conversant with constructor injection, I’ve not been as familiar with setter injection as I’d like to be.

I’ve been learning how to do it, through ServiceManager-aware interfaces, and will share how it’s done, in today’s post.

For configuring objects, reused throughout the application, I’ve found it to be nothing short of amazing. With next to no code, one Module configuration setting, along with the magic of OOP, classes are suitably initialized throughout the application, without any hands-on configuration on my part.

Whilst Zend Framework 2 is great without this. When you start using setter injection, it becomes so much more. In today’s post, I’ll take you through an example which uses setter injection to ensure that the AuthService, or authenticated user object is always available to a class and any of its descendents.

NOTE: There are likely better ways than how I’ve implimented it. But that’s for another day. It makes for a simple example.

How It’s Implimented

There are 3 things which you need to do to have a dependency automatically injected, by Zend Framework, into your class.

  1. Define an interface. This keeps things neat and tidy and really just ensures that your class has a function which can be called by the service manager, to inject the desired dependency
  2. Implement that interface in your class
  3. Define an initializers element in the getServiceConfig method of your module’s Module.php class

Let’s do all three, where I want to have a simple object injected into my class.

The Interface

namespace MaltBlue\Table;

use Zend\Authentication\AuthenticationService;

interface AuthAwareInterface
{
    /**
     * @param $authUser
     * @return mixed
     */
    public function setAuthUser(AuthenticationService $authUser);

    /**
     * @return mixed
     */
    public function getAuthUser();
}

Here I’ve created a simple interface with two methods, setAuthUser which accepts a AuthenticationService object, and getAuthUser. They set and return the auth user object in the implementing class, callable by the ServiceManager.

Implimenting the Interface

use MaltBlue\Table\AuthAwareInterface;

class CacheableTable implements AuthAwareInterface
{
    protected $authUser;

    public function setAuthUser(AuthenticationService $authUser)
    {
        $this->authUser = $authUser;
    }

    public function getAuthUser()
    {
        return $this->authUser;
    }
}

This class, is intended as a base implementation of the TableGateway pattern, which impliments the new Interface.

Initializers Configuration

MaltBlue\Table\AuthAwareInterface

public function getServiceConfig()
{
    return array(
        // ... existing code ...
        'initializers' => array(
            'AuthAwareInterface' => function($model, $serviceLocator) {
                if ($model instanceof AuthAwareInterface) {
                    $authObj = $serviceLocator->get('MaltBlue\AuthService');
                    $model->setAuthUser($authObj);
                }
            }
        ),
    )
}

Finally, we have the module configuration in Module.php. Here, in an initializers element, I’ve specified the interface name and defined what will happen, when a class implimenting it is encountered by the ServiceManager, via a closure.

Simply, if the class implements AuthAwareInterface, then MaltBlue\AuthService is retrieved and set in the object via calling setAuthUser. What I like about it is that there’s not a lot of work, for a lot of gain, and it’s very clear, very structured in the implimentation.

Now, just retrieve your instantiated objects, via the ServiceManager, and they’ll have the dependencies injected for you.

A Word of Warning

I need to point out that you should use setter injection with some care, always being very methodical about it. If the configuration were disparate, or not managed through a framework, it would get very difficult to keep track of what was being injected, when, and where, leading to a very hard to maintain application.

The way that Zend Framework 2’s designed, having a specific ‘initializers’ section of the getServiceConfig return array, does a lot of this for you. If you manage setter injection in a single location, as Zend Framework 2 can, to me that’s fine. Because you only have one place to look to know what’s being injected. But please bear this in mind when using it.

Why the Warning?

The reason for this warning was because of a Twitter conversation I had with Ocramius, which started as follows:

@maltblue yes, magic works until it works fine – that’s common. The problem comes up when it doesn’t work ;-) #php #di

I’ll write more about the downside of setter injection soon. But for the time being, please handle with care. A big thanks to Ocramius for being a great mentor on this topic.

Over To You

So, what do you think? Have you experience with it already? What do you think? Are you keen to use them, despite the objections? Share your feedback in the comments.

Want to Know More?

If you’d like to know more about setter injection vs. constructor injection, here are some excellent links:

Intermediate Patterns Tutorial Zend Framework
  • michl

    How does this affect the overall performance of your application?

    By the way: I think you would still be able to cache your service manager configs with this approach!

    module.php :


    public function getConfig()
    {
    return array_merge(
    include __DIR__ . '/config/module.config.php',
    include __DIR__ . '/config/services.config.php',
    include __DIR__ . '/config/forms.config.php',
    include __DIR__ . '/config/hydrators.config.php',
    include __DIR__ . '/config/inputfilters.config.php',
    include __DIR__ . '/config/validators.config.php',
    include __DIR__ . '/config/filters.config.php',
    include __DIR__ . '/config/viewhelpers.config.php',
    );
    }

    public function getServiceConfig()
    {
    return array(
    'initializers' => array(
    'AuthAwareInterface' => function($model, $serviceLocator) {
    if ($model instanceof AuthAwareInterface) {
    $authObj = $serviceLocator->get('MaltBlueAuthService');
    $model->setAuthUser($authObj);
    }
    }
    ),
    );
    }

    looks interesting!

    • michl

      just tested this and it works even with caching enabled!

      Performance is a BIG issue tho!
      This approach should not be used on non-shared services.. IMO.
      Or not recklessly, at least.

      • michl

        Just found jut about initializer Interface. Initializers and cached configurations have always been working then. Didnt Know That.

    • http://www.matthewsetter.com/ Matthew Setter

      Hi Michl, I’ll reply to all your comments here. I wouldn’t have expected the service config example in this post to be able to be cached, well, outside of APC or OPcache that is, as it uses a closure. That’s ok if you’re only using a single machine, but it wouldn’t be that great when using Memcached etc.

      That being said, it was designed only as an explanatory example, not necessarily something to be used in production. Thanks for giving such detailed feedback to the post. I think I might have to start distinguishing between example posts and ones that can be used in production.

      • michl

        Thanks For your reply.

        Actually i disabandoned the idea of using initializers due to the fact That closures can not be serialized. I did not know there was an initializerinterface which enables you to actually use real initializer classes instead of closures. So I was quite happy when I found out caching still works … Until I realized it always has ;)

        Nevertheless I think you still should mention the performance issues that could raise when using initializers on non shared services!

        Regarding your comment on example and real world code:
        This is something I think zf2 beginners are confused by, a lot.
        They are the ones who cannot immediately differ between “dirty” example code and clean production code. So yes, I think beginners would benefit most from outlining the differences.

        • http://www.matthewsetter.com/ Matthew Setter

          Thanks again Michl. I’ll work on something which can simply communicate, or indicate, the different considerations needed. I’m still refining the look of the new site, but will implement something which helps communicate the difference.

          Perhaps a simple table or highlighted div.

  • Pingback: Problem mit Software-Design (Dependency Injection) - Zend Framework Forum - ZF1 / ZF2