Saturday, September 21, 2013

How to Pass the ServiceManager to Forms and Taking Advantage of the init()

Seems pretty easy and its also documented in the ZF2 docs but I think its worth repeating here.

In short, when creating forms by extending the Form object it is a good practice to create your elements inside the init() method.

namespace class\name\space;
class MyForm extends Form implements ServiceLocatorAwareInterface
{
    protected $serviceLocator;

    public function __construct()
    {
        $this->setName('MyForm');
    }

    public function init()
    {
        $this->add(array(
            'type' => 'MyCustomFieldset', // You are not using a class name space,
            'name' => 'custom_fieldset',
        ));

        $this->add(array(
            'type' => 'Select',
            'name' => 'custom_select',
            'options' => array(
                'value_options' => $this->getSelections(), // you can access methods via the ServiceManager now!
            ),
        ));
    }

    public function getSelections()
    {
         $mainServiceManager = $this->getServiceLocator()->getServiceLocator();
         
         return $mainServiceManager->get('someService')
                                   ->someMethodThatCreateSelectionArray();
    }

    public function getServiceLocator()
    {
        return $this->serviceLocator;
    }

    public function setServiceLocator(ServiceLocator $serviceLocator)
    {
        $this->serviceLocator = $serviceLocator;
    }
}
As you see from above, you have access now to the ServiceManager. You can even use $this->getSelections(). You will not be able to do that inside the __construct()!

Of course, you have to set this up inside Module.php via the Form Element Manager
//Module.php

public function getFormElementConfig()
{
    return array(
        'invokables' => array(
            'MyForm' => 'class\name\space\MyForm',
            'MyCustomFieldset' => 'class\name\space\MyFieldset',
        )
    );
}
To access the form, simply pull it from the Form Element Manager
// some controller
public function indexAction()
{
    $form = $this->getServiceLocator()->get('FormElementManager')->get('MyForm');
    return ViewModel(array(
        'form' => $form,
    ))
}

Saturday, September 7, 2013

How to Create a Custom ZF2 ServiceManager Plugin?

If you want to discover how a ServiceManager Plugin gets initialized go here:
http://www.franzdeleon.me/2013/09/how-does-servicemanager-plugin-get.html

So how do we register our own custom ServiceManager Plugin?

1.) First create a Plugin Manager:

use Zend\ServiceManager\AbstractPluginManager;
class CustomPluginManager extends AbstractPluginManager
{
    public function validatePlugin($plugin)
    {
        if (!$plugin instanceof CustomeInterface) {
            throw new ErrorException('Wrong intance of plugin');
        }
    }
}
2.) Then create a ServiceManager Factory that returns an instance of your just created CustomPluginManager:

use Zend\Mvc\Service\AbstractPluginManagerFactory;
class CustomManagerFactory extends AbstractPluginManagerFactory
{
    const PLUGIN_MANAGER_CLASS = 'Namespace\to\CustomPluginManager';
    public function createService(ServiceLocatorInterface $serviceLocator)
    {
        $plugins = parent::createService($serviceLocator);
        return $plugins;
    }
}
3.) Now lets define an interface for our plugin config:

interface CustomPluginProviderInterface
{
    public function getCustomPlugin();
}

4.) Then we need to put this all together by registering our CustomPlugin plugin in Module.php. We first need to register our Factory:

//inside Module.php
public function getServiceConfig()
{
    return array(
        'factories' => array(
            'customPluginManager' => 'namespace\to\CustomManagerFactory',
        ),
    )
}
5.) In order for the Plugin to be created as a plugin manager, we need to register it to the Service Listener. We do this via init() method. It is important to put this inside init() as this is called before the MVC bootstrapping.

// inside Module.php
public function init(ModuleManager $moduleManager)
{
    $sm = $moduleManager->getEvent()->getParam('ServiceManager');
    $serviceListener = $sm->get('ServiceListener');
    $serviceListener->addServiceManager(
        'customPluginManager',
        'custom_plugin_config',
        'namespace\to\CustomPluginProviderInterface',
        'getCustomPluginConfig'
    );
}
6.) Now you can register to your custom plugin from other modules!
// in some other Module.php
public function getCustomPluginConfig()
{
    return array(
        'invokables' => array(
            'AclService' => 'namespace/to/AclService'
        )
    )
}
7.) Then you can call it for example inside a controller:
 
// inside controller.php in some other module
public function someAction()
{
    $aclService = $this->getServiceLocator()->get('customPluginManager')->get('AclService');
}

That is pretty much it!

Reference:
http://raing3.gshi.org/2013/05/26/creating-custom-plugin-manager-in-zend-framework-2/

How Does a ServiceManager Plugin gets Initialized?

Hopefully this post will explain and answer this question.

For this example, I will trace the initialization of the FilterManager Plugin.
For those who are not familiar with the plugin functionality of ZF2, it is basically an extension of the Service Manager that you can use for organization and other things.

As an example, you are calling a plugin manager when you do something like below:

// from Controller
$stringTrimFilter = $this->getServiceLocator()->get('FilterManager')->get('stringTrim');

// from inside a View. basePath is a View Helper Plugin
$this->basePath('/some/uri');

Now lets get down to business. So how does a plugin gets created and initialized?

1.) We will start from the ModuleManagerFactory for simplicity's sake. In the ModuleManagerFactory (https://github.com/zendframework/zf2/blob/master/library/Zend/Mvc/Service/ModuleManagerFactory.php) the Service Listener Factory gets initialized. (see line 38 and called on 44)

2.) In the Service Listener Factory (https://github.com/zendframework/zf2/blob/master/library/Zend/Mvc/Service/ServiceListenerFactory.php) a bunch of Plugin Factories gets initialized also.

One of the plugin factories that gets initialized is the FilterManager factory which looks like this. See line 54:

'FilterManager' => 'Zend\Mvc\Service\FilterManagerFactory',

3.) Lets go to the FilterManagerFactory (https://github.com/zendframework/zf2/blob/master/library/Zend/Mvc/Service/FilterManagerFactory.php).The class is basically a ServiceManager Factory implementation class.

4.) The FilterManagerFactory file extends AbstractPluginManagerFactory (https://github.com/zendframework/zf2/blob/master/library/Zend/Mvc/Service/AbstractPluginManagerFactory.php) which is basically an implementation of FactoryInterface.php. That means we are instantiating an object. What object?

5) The FilterManagerFactory will INSTANTIATE and RETURN an instance of FilterPluginManager (https://github.com/zendframework/zf2/blob/master/library/Zend/Filter/FilterPluginManager.php) see line 17:

const PLUGIN_MANAGER_CLASS = 'Zend\Filter\FilterPluginManager';

6.) Now an instance of FilterPluginManager is created. It is important to note that FilterPluginManager extends AbstractPluginManager (https://github.com/zendframework/zf2/blob/master/library/Zend/ServiceManager/AbstractPluginManager.php) which is basically an own ServiceManager class!

The FilterPluginManager class also invokes a bunch of ZF2 filters. The class also gets mapped to the service key "FilterManager" key. Take a look at step 2 again.

Moreover this class implements the abstract method validatePlugin() which is used to validate the actual plugin object that you will inject to this manager like for example, StringTrim!

    public function validatePlugin($plugin)
    {
        if ($plugin instanceof FilterInterface) {
            // we're okay
            return;
        }
        if (is_callable($plugin)) {
            // also okay
            return;
        }
        throw new Exception\RuntimeException(sprintf(
            'Plugin of type %s is invalid; must implement %s\FilterInterface or be callable',
            (is_object($plugin) ? get_class($plugin) : gettype($plugin)),
            __NAMESPACE__
        ));
    }

7.) Now we are back in the ModuleManagerFactory (see step 1). Since we now have an instance of the FilterPluginManager via the key FilterManager, this instance is added to the Service Listener (see line 76 of ModuleManagerFactory.php) or below:

$serviceListener->addServiceManager(
    'FilterManager',
    'filters',
    'Zend\ModuleManager\Feature\FilterProviderInterface',
    'getFilterConfig'
 );

8.) Lets take a look at the FilterProviderInterface (https://github.com/zendframework/zf2/blob/master/library/Zend/ModuleManager/Feature/FilterProviderInterface.php) which difines the method getFilterConfig(). What is this method again? This is where you register your custom filters remember in Module.php?
public function getFilterConfig()
{
    return array(
        'factories' => array(
            'customFilter' => function ($sm) {
                // create your filter...
            }
        ),         
    )
}

9.) Thats pretty much the workflow and registration of a ServiceManager Plugin. Now you can call it like this:

$stringTrimFilter = $this->getServiceLocator()->get('FilterManager')->get('stringTrim');
// or your custom filter
$customfilter = $this->getServiceLocator()->get('FilterManager')->get('customFilter');