automatix
automatix

Reputation: 14552

How to move listener code from closure to method and pass arguments to it in ZF2?

I have a Module class with multiple "inline" event listeners in its onBootstrap(...):

...
class Module
{
    public function onBootstrap(MvcEvent $mvcEvent)
    {
        ...
        $foo = ...;
        ...
        $halPlugin = $viewHelperManager->get('Hal');
        $halPlugin->getEventManager()->attach('bar', function ($event) {
            ...
        });
        ...
        $halPlugin->getEventManager()->attach('baz', function ($event) use ($foo)
        {
            ...
        });
        ...
    }
    ...
}

Now, in order to hold/make Module#onBootstrap(...) slim, I want to move the listeners from the closures to separate methods. It's no problem for the onBar event listener. But it doesn't work for onBaz, that needs additional input:

...
class Module
{
    public function onBootstrap(MvcEvent $mvcEvent)
    {
        ...
        $halPlugin->getEventManager()->attach('bar', [$this, 'onBar']);
        ...
    }
    ...
    public function onBar()
    {
        ...
    }
    public function onBaz() // <-- How to pass $foo to the method?
    {
        // Some stuff, that needs $foo...
        ...
    }
}

In the original variant this input was being passed into the closure via the use directive. But how to do this now?

How to move the logic of an event listener (attached in the Module#onBootstrap(...)) from a closure with a use statement to a separate method and pass arguments to it?

Upvotes: 1

Views: 151

Answers (2)

Otto Sandstr&#246;m
Otto Sandstr&#246;m

Reputation: 745

What you can do is move the foo as property of your module class and then access it with $this->foo

class Module
{
    private $foo;

    public function onBootstrap(MvcEvent $mvcEvent)
    {
        $this->foo = ...;
        $halPlugin->getEventManager()->attach('baz', [$this, 'onBaz']);
        ...
    }
    ...
    public function onBaz()
    {
        $this->foo
        // Some stuff, that needs $foo...
        ...
    }
}

You can also make the function in module return a Closure where you use $foo sent in with the function.

class Module
{
    public function onBootstrap(MvcEvent $mvcEvent)
    {
        $foo = ...;
        $halPlugin->getEventManager()->attach('baz', $this->onBaz($foo));
        ...
    }
    ...
    public function onBaz($foo)
    {
        return function ($event) use ($foo) {
            $this->foo
            // Some stuff, that needs $foo...
            ...
        }
    }
}

Upvotes: 1

Crisp
Crisp

Reputation: 11447

Move your method to a listener class.

A listener just needs to be Callable, which means declaring the__invoke() method in your class and placing all the code you have in your current onBaz() method

<?php

namespace SomeModule\Listener;

class BazListener
{

    protected $foo;

    public function __construct($foo)
    {
        $this->foo = $foo;
    }

    public function __invoke()
    {
         // code from onBaz() method using $this->foo
    }
}

You'll note that $foo is now a dependency requiring constructor injection. To fulfil the dependency write a factory for your listener class, fetch $foo in the factory and inject it there.

<?php

namespace SomeModule\Factory;

use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;

class BazListenerFactory implements FactoryInterface
{
     public function createService(ServiceLocatorInterface $services)
     {
          //fetch foo from wherever ..
          $foo = ;
          return new \SomeModule\Listener\BazListener($foo);
     }
}

Now add the listener factory to the service manager so you can access it in onBootstrap

return array(
     'service_manager' => (
         'factories' => (
             // ..
             'SomeModule\Listener\BazListener' => 'SomeModule\Factory\BazListenerFactory',
             // ..
         ),
    ),
);

Finally fetch your listener as a service from the service manager in onBootstrap and attach it to your event manager

public function onBootstrap(MvcEvent $mvcEvent)
{
    $bazListener = $mvcEvent->getApplication()
                            ->getServiceManager()
                            ->get('SomeModule\Listener\BazListener');
    $halPlugin->getEventManager()->attach('bar', $bazListener);
    ...
}

It's a bit of extra work, but it better separates concerns and you now have a standalone listener that you could potentially reuse instead of relying on a copy/pasted method from a Module class.

Upvotes: 2

Related Questions