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');

Tuesday, August 27, 2013

TIP: How to Immitate init() inside Controllers in ZF2

For ZF1 users there is the convenient init() method that you can use on every controller but in ZF2 the init() does not exist anymore for design purposes.

There are sometimes advantages though of having an init() type functionality inside the constructor. For example, checking the ACL.

A little background. One of the problems of working directly inside the constructor in ZF2 is because the dispatch event has not occured yet, you cannot grab important objects like controller plugins so doing the bellow wont work.
// assume we are inside a controller
public function __construct()
{
    $this->redirect()->toRoute('someroute'); // I will not work!
}
Solution:
There are tons of ways to do this but I find this a little easier:
class IndexController extends AbstractActionController
{
    public function __construct()
    {
        $resource = $this->getResourceId();
        $this->getEventManager()->attach('dispatch', function ($e) use ($resource) {
            $controller = $e->getTarget(); // this will return your controller instance

            // there is no ACL plugin ok! I created that!
            if (!$controller->acl()->isAllowed('me', $resource)) {
                // now i can use controller plugins!
                return $controller->redirect()->toRoute('authenticate');
            }     
        });
    }

    public function getResourceId()
    {
        return 'some_resource';
    }
}
Reference:
http://mwop.net/blog/2012-07-30-the-new-init.html

TIP: How to Retrieve and Register Custom Controller Plugins

You can think of controller plugins as action helpers in ZF1.
In order to retrieve the list of plugins including custom plugins you created.

Create a custom plugin:
//module.php
class Module
{
    public function getControllerPluginConfig()
    {
        return array(
            'factories' => array(
                'MyCustomAclPlugin' => function ($sm) {
                    $supahService = $sm->getServiceLocator()->get('superService');
                    return new MyCustomAclService($supahService);
                },
            )
        );
    }
}
Now to use it:
// pretend we are in a controller that extands AbstractActionController
public function indexAction() {
    var_dump($this->getPluginManager()->getRegisteredServices());
    // array('mycustomaclplugin', ...);

    // using our plugin
    $this->mycustomaclplugin()->someMethod();
}

Routing URI With Forward Appended Slashes in ZF2

You may sometimes want to route forward appended URIs to its last corresponding action.

For example:

http://www.sample.com/mycontroller/login/ (WILL NOT route to login action)
http://www.sample.com/mycontroller/login (WILL ROUTE to login action)
http://www.sample.com/mycontroller/ (WILL NOT ROUTE to index action)
http://www.sample.com/mycontroller (WILL ROUTE to index action)

And assume this Route setting for above:

array(
    'mycontroller' => array(
        'type'    => 'Segment',
        'options' => array(
            'route'    => '/mycontroller',
            'defaults' => array(
                'controller' => 'SomeModule\Controller\MyController',
                'action'     => 'index',
            ),
        ),
        'may_terminate' => true,
        'child_routes' => array(
            'login' => array(
                'type'    => 'Segment',
                'options' => array(
                    'route'    => '/login',
                    'defaults' => array(
                        'controller' => 'SomeModule\Controller\MyController',
                        'action'     => 'login',
                    ),
                ),
                'may_terminate' => true,
            ),
        ),
    ),
);

To make the forward appended slashes default to the last action in the URI:
array(
    'mycontroller' => array(
        'type'    => 'Segment',
        'options' => array(
            'route'    => '/mycontroller',
            'defaults' => array(
                'controller' => 'SomeModule\Controller\MyController',
                'action'     => 'index',
            ),
        ),
        'may_terminate' => true,
        'child_routes' => array(
            'default' => array(
                'type'    => 'Segment',
                'options' => array(
                    'route'    => '/[:action][/]',
                    'constraints' => array(
                        'action'     => '[a-zA-Z][a-zA-Z0-9_-]+',
                    ),
                    'defaults' => array(
                        'controller'    => 'SomeModule\Controller\MyController',
                        'action'        => 'index',
                    ),
                ),
                'may_terminate' => true,
            ),
            'login' => array(
                'type'    => 'Segment',
                'options' => array(
                    'route'    => '/login[/]',
                    'defaults' => array(
                        'controller' => 'SomeModule\Controller\MyController',
                        'action'     => 'login',
                    ),
                ),
                'may_terminate' => true,
            ),
        ),
    ),
);

Notice that I added a "default" child route so that I will not have to worry if a I added a new action and forget to create a route for it. Also notice the "[/]" which means that forward slashes are optional now and that's basically it. You just have to add "[/]" in each route of a Segment type.

http://www.sample.com/mycontroller/login/ (WILL ROUTE to login action)
http://www.sample.com/mycontroller/login (WILL ROUTE to login action)
http://www.sample.com/mycontroller/ (WILL ROUTE to index action)
http://www.sample.com/mycontroller (WILL ROUTE to index action)

If you dont want a default fallback route, you can do just so.
array(
    'mycontroller' => array(
        'type'    => 'Segment',
        'options' => array(
            'route'    => '/mycontroller[/]',
            'defaults' => array(
                'controller' => 'SomeModule\Controller\MyController',
                'action'     => 'index',
            ),
        ),
        'may_terminate' => true,
        'child_routes' => array(
            'login' => array(
                'type'    => 'Segment',
                'options' => array(
                    'route'    => '/login[/]',
                    'defaults' => array(
                        'controller' => 'SomeModule\Controller\MyController',
                        'action'     => 'login',
                    ),
                ),
                'may_terminate' => true,
            ),
        ),
    ),
);
Notice that I added "[/]" in the parent route because we removed the default child fallback route.
Now your route with forward slashes should work.

Monday, August 26, 2013

ZF2's MVC Event Trigger Sequence

Below is the trigger sequence of Zend Frameworks 2's MVC Event.

1.) MvcEvent::EVENT_BOOTSTRAP
    returns: Zend\Mvc\Application

    a.) MvcEvent::EVENT_ROUTE
        Returns: Zend\Mvc\Application

    b.) MvcEvent:EVENT_DISPATCH
        Returns: Zend\Mvc\Controller\AbstractController

    c.) MvcEvent:EVENT_RENDER
        Returns: Zend\Mvc\Application

    d.) MvcEvent:EVENT_FINISH
        Returns: Zend\Mvc\Application

You will usually attach to an event inside onBootstrap($e) in Module.php
//Module.php
namespace SomeModule;
use Zend\Mvc\MvcEvent;

class Module
{
    public function onBootstrap($e)
    {
        $e->getName(); // returns MvcEvent::EVENT_BOOTSTRAP
        $e->getTarget(); // returns Zend\Mvc\Application

        // retrieves the Event Manager within Bootstrap
        $em = $e->getApplication()->getEventManager();

        $em->attach(MvcEvent::EVENT_ROUTE, function ($e) {
             $e->getName(); // returns MvcEvent::EVENT_ROUTE
             $e->getTarget(); // returns Zend\Mvc\Application
        }

        $em->attach(MvcEvent::EVENT_DISPATCH, function ($e) {
             $e->getName(); // returns MvcEvent::EVENT_DISPATCH
             $e->getTarget(); // returns Zend\Mvc\Controller\AbstractController
        }

        $em->attach(MvcEvent::EVENT_RENDER, function ($e) {
             $e->getName(); // returns MvcEvent::EVENT_RENDER
             $e->getTarget(); // returns Zend\Mvc\Application
        }

        $em->attach(MvcEvent::EVENT_FINISH, function ($e) {
             $e->getName(); // returns MvcEvent::EVENT_FINISH
             $e->getTarget(); // returns Zend\Mvc\Application
        }
    }
}
It is only normal to return Zend\Mvc\Application as the object itself is being manipulated in the application process.