Welcome to Abdul Malik Ikhsan's Blog

Zend Framework 2 ‘Cheat Sheet’ : Service Manager

Posted in Teknologi, Tutorial PHP, Zend Framework 2 by samsonasik on January 2, 2013

zf2-zendframework2The Service Locator design pattern is implemented by the ServiceManager. The Service Locator is a service/object locator, tasked with retrieving other objects.

Get the SM :
1. Inside Controller

$serviceLocator = $this->getServiceLocator();

2. Inside Module.php

namespace YourModule\Service;

use Zend\Mvc\MvcEvent;

class Module
{
    public function onBootstrap(MvcEvent $e)
    {
        $sm = $e->getApplication()->getServiceManager();
    }
}

3. Inside Controller Plugin

$serviceLocator = $this->getController()->getServiceLocator();

Types of Services
The registered name of service is not case sensitive. There are the type of services :
a. invokables : an array of service name/class name pairs. The class name should be class that may be directly instantiated without any constructor arguments
for ex :

//YourModule/config/module.config.php
return array(
    'controllers'=>array(
        'invokables' => array(
            'SanUser\Controller\User' => 'SanUser\Controller\UserController'
        ),
    ),
);

b. abstract_factories : Unknown Services ( The “Limbo” if ServiceManager failed to search in registered services)

//YourModule/src/YourModule/Service/CommonControlAppAbstractFactory.php
namespace YourModule\Service;

use Zend\ServiceManager\AbstractFactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;

class CommonControlAppAbstractFactory implements AbstractFactoryInterface
{
    public function canCreateServiceWithName(ServiceLocatorInterface $locator, $name, $requestedName)
    {
        if (class_exists($requestedName.'Controller')){
            return true;
        }

        return false;
    }

    public function createServiceWithName(ServiceLocatorInterface $locator, $name, $requestedName)
    {
        $class = $requestedName.'Controller';
        return new $class;
    }
}

Then, register in SM :

//YourModule/config/module.config.php
return array(
    'controllers'=> array(
	    'abstract_factories' => array(
		'YourModule\Service\CommonControlAppAbstractFactory',
	    ),
	);
    ),
);

In this case, if SM could not find controllers in invokables, the SM will turn to it whenever canCreateServiceWithName return true; ( controllers is service that called automatically by mvc stack )
What if you want other service ? This is it :

namespace YourModule\Service;

use Zend\ServiceManager\AbstractFactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;

class CommonModelTableAbstractFactory implements AbstractFactoryInterface
{
    public function canCreateServiceWithName(ServiceLocatorInterface $locator, $name, $requestedName)
    {
        return (substr($requestedName, -5) === 'Table');
    }

    public function createServiceWithName(ServiceLocatorInterface $locator, $name, $requestedName)
    {
        $db = $locator->get('Zend\Db\Adapter\Adapter');
        $tablemodel = new $requestedName;
        $tablemodel->setDbAdapter($db);
        
        return $tablemodel;
    }
}

You want if you call un-registered table model, you automatically create service with this abstract factory whenever last 5 chars of the service called = ‘Table’. Register this abstract factory under service_manager key :

//YourModule/config/module.config.php
return array(
    'service_manager'=> array(
	    'abstract_factories' => array(
		'YourModule\Service\CommonModelTableAbstractFactory',
	    ),
	);
    ),
);

Note for abstract_factories : Being explicit is more secure and reliable. You should not forgot to register service you write into ServiceManager.

c. factories : an array of service name/factory class name pairs.
c.1. If you are using PHP configuration files, you may provide any PHP callable as the factory.

//YourModule/config/module.config.php
return array(
    'service_manager'=>array(
        'factories' => array(
            'MyTable' => function ($sm) {
                $db = $sm->get('Zend\Db\Adapter\DbAdapter');
                $table = new \YourModule\Model\MyTableModel();
                $table->setDbAdapter($db);
            },
        ),
    ),
);

c.2. by implementing Zend\ServiceManager\FactoryInterface by create a factory first :

namespace YourModule\Service;

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

class MyTableFactory implements FactoryInterface
{
    public function createService(ServiceLocatorInterface $serviceLocator)
    {
        $db = $serviceLocator->get('Zend\Db\Adapter\DbAdapter');
        $table = new \YourModule\Model\MyTableModel();
        $table->setDbAdapter($db);

        return $table;
    }
}

And the factories registered just like the following :

//YourModule/config/module.config.php
return array(
    'service_manager'=>array(
        'factories' => array(
            'MyTable' => 'YourModule\Service\MyTableFactory'
        ),
    ),
);

d. aliases : which should be an associative array of alias name/target name pairs (where the target name may also be an alias).

//YourModule/config/module.config.php
return array(
    'service_manager'=>array(
        'factories' => array(
            'MyTable' => 'YourModule\Service\MyTableFactory'
        ),
        'aliases' => array(
            'YourModule\Model\MyTable' => 'MyTable',
        ),
    ),
);

e. shared :an array of service name/boolean pairs, indicating whether or not a service should be shared. By default, the ServiceManager assumes all services are shared, but you may specify a boolean false value here to indicate a new instance should be returned.

//YourModule/config/module.config.php
return array(
    'service_manager'=>array(
        'factories' => array(
            'MyTable' => 'YourModule\Service\MyTableFactory'
        ),
        'shared' => array(
            // Usually, you'll only indicate services that should _NOT_ be
            // shared -- i.e., ones where you want a different instance
            // every time.
            'MyTable' => false,
        ),
    ),
);

f. services : an array of service name/object pairs. Clearly, this will only work with PHP configuration.

//YourModule/config/module.config.php
return array(
    'service_manager'=>array(
        'services' => array(
            // Keys are the service names
            // Values are objects
            'Auth' => new YourModule\Authentication\AuthenticationService(),
        ),
    ),
);

g. initializers
It initialize the service whenever service created. It can reduce the redundance the injections to services.

//YourModule/config/module.config.php
return array(
    'service_manager'=>array(
        'initializers' => array(
	    function ($instance, $sm) {
		if ($instance instanceof \Zend\Db\Adapter\AdapterAwareInterface) {
		    $instance->setDbAdapter($sm->get('Zend\Db\Adapter\Adapter'));
		}
	    }
	),
    ),
);

And you should not to inject Adapter manually in the Table Class :

namespace YourModule\Model;

use Zend\Db\TableGateway\AbstractTableGateway;
use Zend\Db\Adapter\AdapterAwareInterface;
use Zend\Db\Adapter\Adapter;

class UserTable extends AbstractTableGateway
    implements AdapterAwareInterface
{
    protected $table = 'zf2_users';

    public function setDbAdapter(Adapter $adapter)
    {
        $this->adapter = $adapter;
        $this->initialize();
    }
}

Just invoke at service_manager :

//YourModule/config/module.config.php
return array(
    'service_manager'=>array(
        'initializers' => array(
	    function ($instance, $sm) {
		if ($instance instanceof \Zend\Db\Adapter\AdapterAwareInterface) {
		    $instance->setDbAdapter($sm->get('Zend\Db\Adapter\Adapter'));
		}
	    }
	),
        'invokables' => array(
            'YourModule\Model\UserTable' => 'YourModule\Model\UserTable'
        )
    ),
);

h. allow_override
Override your existing Services.

//YourModule/config/module.config.php
return array(
    'service_manager'=>array(
        'factories' => array(
            'MyService' => 'YourModule\Service\MyServiceFactory'
        ),
        'allow_override' => array(
            'MyService' => true,
        ),
    ),
);

The top level configuration keys

Manager Key name in configuration array Method name in Module.php
ServiceManager service_manager getServiceConfig()
ViewHelperManager view_helpers getViewHelperConfig()
ControllerPluginManager controller_plugins getControllerPluginConfig()
ControllerLoader controllers getControllerConfig()

Sample Code in Module.php :

class Module
{
    //for 'service_manager'
    public function getServiceConfig()
    {
        return array(
            'invokables' => array( /* see Types of Services */  ),
            'factories' => array( /* see Types of Services */ ),
            'abstract_factories' => array( /* see Types of Services */ ),
            'aliases' => array( /* see Types of Services */ ),
            'services' => array( /* see Types of Services */ ),
            'initializers' => array( /* see Types of Services */ ),
            'shared' => array(/* see Types of Services */),
        );
    }

    //for 'controllers' -> it automatically composed by mvc stack
    //no need to call by your hand ( get('...') );
    public function getControllerConfig()
    {
        return array(
            /* looks like above code */
        );
    }

    //for 'controller_plugins'
    public function getControllerPluginConfig()
    {
        return array(
            /* looks like above code */
        );
    }

    //for 'view_helpers' call in view by $this->nameViewHelperRegistered()->dosomething()
    public function getViewHelperConfig()
    {
        return array(
            /* looks like above code */
        );
    }
}

For ZF 2.1 ( still in dev branch, the keys added with FormElementManager )

Manager Key name in configuration array Method name in Module.php
FormElementManager form_elements getFormElementConfig()

For what have to do to create a Controller pLugin , see : empirio’s post about creating Controller pLugin in ZF2. For what have to do to create a View Helper , see : my post about creating view helper or EvanDotPro’s post.

Hope this post helpful ;). Happy new Year!

References :
1. http://zf2.readthedocs.org/en/latest/modules/zend.service-manager.intro.html
2. http://akrabat.com/zend-framework-2/zendservicemanager-configuration-keys/
3. http://juriansluiman.nl/en/article/120/using-zend-framework-service-managers-in-your-application
4. http://blog.evan.pro/creating-a-simple-view-helper-in-zend-framework-2
5. http://lab.empirio.no/custom-controller-plugin-in-zf2.html

Advertisements

53 Responses

Subscribe to comments with RSS.

  1. Mungiu said, on January 9, 2013 at 9:30 pm

    very nice

  2. Mungiu said, on January 10, 2013 at 5:09 am

    Maybe you know, how can i add option to a select from a collection?
    I try this $form->get(“faqs”)->getTargetElement(“question”)->get(“question”)->setValueOptions($faqs);
    but dont’t work, if i try let say setName it’s working but with this i receive:
    Argument 1 passed to Zend\Form\View\Helper\FormCollection::__invoke() must implement interface Zend\Form\ElementInterface, array given in /var/www/cekaut/vendor/zendframework/zendframework/library/Zend/Form/View/Helper/FormCollection.php on line 122

    • samsonasik said, on January 10, 2013 at 6:01 am

      i think it should be :

      $form->get('faqs')->get('question')->get('question_element')->setValueOptions($faqs);
      
  3. Mungiu said, on January 10, 2013 at 5:54 am

    i want to use same collection of element several times but with select options different, any advise please?

  4. Mungiu said, on January 10, 2013 at 6:07 am

    let me give soem details:
    i have a fieldset where i add my colelction:
    $this->add(array(
    ‘type’ => ‘Zend\Form\Element\Collection’,
    ‘name’ => ‘faqs’,
    ‘options’ => array(
    ‘label’ => ”,
    ‘count’ => 1,
    ‘should_create_template’ => true,
    ‘allow_add’ => true,
    ‘target_element’ => array(
    ‘type’ => ‘Catalog\Form\AddFaqs’
    )
    )
    ));
    The element in the collection is:
    $this->add(array(
    ‘type’ => ‘\Zend\Form\Element\Select’,
    ‘name’ => ‘question’,
    ‘attributes’ => array(
    ‘class’ => ‘title-w200’
    ),
    ‘options’ => array(
    ‘label’ => ‘Durata maxima de livrare’,
    ‘value_options’ => array(
    ” => ‘-‘,
    1 => ‘1 saptamana’,
    2 => ‘2 saptamani’,
    3 => ‘1 luna’,
    ),
    ),
    ));

    So faqs is the collection, don’t have any element only target elements so that function only works getTargetElements, but when i get the actual select element receice thar error what i wrote above.

  5. Mungiu said, on January 10, 2013 at 6:10 am

    so i want to use same collection with seelct changed what sould i do, actual optopn is to make five collection but looks silly doing this way

  6. Mungiu said, on January 10, 2013 at 7:05 am

    anyway tanks for take the time to read my q

  7. Vincent Roman (@vincentstinks) said, on March 9, 2013 at 6:53 am

    Do you have any working examples of dependency injection for fieldsets where a db connection needs to be made to populate a select menu? Thanks in advance =)

  8. rodrigo said, on April 2, 2013 at 10:39 am

    Hi Abdul 🙂 what about the ValidatorPluginManager? i have tried to create a factory for a custom validator but it doesn’t work

    ‘validators’ => array(
    ‘factories’ => array(
    ‘Application\Validator\Custom’ => function($sm) {
    $validator = new \Application\Validator\Custom;
    $validator->setSomething($sm->get(‘something’));
    return $validator;
    },
    )
    ),

    Any ideas? 🙂

    • samsonasik said, on April 2, 2013 at 6:01 pm

      just work for me, try call it by :

              $mycustomvalidator = $this->getServiceLocator()
                                       ->get('ValidatorManager')
                                       ->get('Application\Validator\Custom');
      
      • Adrian said, on July 11, 2013 at 7:02 pm
        return array(
            'service_manager' => array(
                'factories' => array(
                    'Zend\Db\Adapter\Adapter' => function ($sm){
                        $config = $sm->get('config');
                        $adapter = new BjyProfiler\Db\Adapter\ProfilingAdapter($config['db']);
                        $adapter->setProfiler(new BjyProfiler\Db\Profiler\Profiler);
                        $adapter->injectProfilingStatementPrototype();
                        return $adapter;
                    },
                    'DoctrineUniqueObject' => function ($sm){
                        $uniqueObject = new DoctrineModule\Validator\UniqueObject(array(
                            'fields' => 'name',
                            'object_repository' => $sm->get('Doctrine\ORM\EntityManager')->getRepository('Inventory\Entity\Owners'),
                            'object_manager' => $sm->get('Doctrine\ORM\EntityManager'),
                        ));
                        return $uniqueObject;
                    }
                )
            ),
        );
        
        
        Ocranimus said it's correct but do you know maybe how to inject this DoctrineUniqueObject into entity annotation in order to create form fields. My annotation looks like this:
        
        
            /** 
             * @ORM\Column(type="string", length=45, nullable=false, name="name")
             * @Annotation\Required(true)
             * @Annotation\Attributes({"type":"text"})
             * @Annotation\Options({"label":"Label", "label_attributes":{"class":"control-label"}})
             * @Annotation\Filter({"name":"StringTrim"})
             * @Annotation\Validator({"name":"DoctrineUniqueObject"})
             */
            private $name;
        
      • samsonasik said, on July 12, 2013 at 1:02 am

        register under ‘validators’ key. or register at Module::getValidatorConfig()

      • Adrian said, on July 12, 2013 at 4:55 am

        http://pastebin.com/JkZmi6DR

        Tried these ways – none worked. Still getting Zend\Validator\ValidatorPluginManager::get was unable to fetch or create an instance for DoctrineUniqueObject

        Maybe there’s something I should use?
        I tried

        namespace Inventory;
        
        use Zend\Mvc\MvcEvent;
        use Zend\Validator\Translator\TranslatorAwareInterface as Translator;
        use Zend\Validator\AbstractValidator;
        use FormAbstractServiceFactory;
        

        also don’t work

        I thought @Annotations would be the easiest, simplest way ;(

      • samsonasik said, on July 13, 2013 at 12:35 am

        I have no idea, sorry 🙂

    • Adrian said, on July 28, 2013 at 6:51 pm

      Hi again.

      Here are some pastes.

      Owners Entity http://pastebin.com/seuZHWq8
      Module.php http://pastebin.com/DjKsVMPL
      OwnerController http://pastebin.com/pgerFhkS

      Maybe when you look at those You’ll figure it out what’s wrong or missing.

      Thanks in advance.

  9. Onsite said, on April 3, 2013 at 10:24 pm

    I’m not sure if I good understand what does ServiceManager do.
    In module.config.php I have following code:
    return array(
    ‘controllers’ => array(
    ‘invokables’ => array(
    ‘MyModule\Common\CustomAuth’ => ‘MyModule\Common\CustomAuth’,
    ),
    ),
    )
    In module.php I have:
    public function onBootstrap($e)
    {
    $customAuth = $e->getApplication()->getServiceManager()->get(‘MyModule\Common\CustomAuth’);
    }
    And I got error:
    ‘Zend\ServiceManager\ServiceManager::get was unable to fetch or create an instance
    Shouldn’t I get instance of MyModule\Common\CustomAuth?

    • samsonasik said, on April 4, 2013 at 4:49 am

      what Manager you want ? what ‘MyModule\Common\CustomAuth’ is ? a controller_plugins ? if yes, you should register at ‘controller_plugins’ key. if you want to call controller_plugin, you should call via ControllerPluginManager.

      $e->getApplication()->getServiceManager()->get('ControllerPluginManager')->get('myauth'); 
      

      if it is a ‘controllers’, you should place it at Controller namespace to make it readable and add suffix Controller to make it more more more readable. if you want to call, you can call by ControllerLoader but i’m not suggest using it because already executed by mvc stack, why call it ? :p, use EventManager ! 😉

      • Onsite said, on April 4, 2013 at 11:09 pm

        Thanks!
        It works great now. 🙂

      • samsonasik said, on April 5, 2013 at 12:18 am

        you’re welcome 😉

  10. […] Zend Framework 2 ‘Cheat Sheet’ : Service Manager von Abdul Malik Ikhsan's Blog […]

  11. […] Abdul Malik Ikhsan's ServiceManager Cheat Cheat […]

  12. Magic said, on May 29, 2013 at 10:20 pm

    Can you give me a hand. I want to use serviceLocator in the layout.phtml. And specifically in the section where I make the css and javascript files including. From the service locator want to to take a parameter which will show me the skin name, and knowing that I will include different css and javascript ..etc files. But for now I cant find a way to use it in this section of the layout.phtml file. Can you propose a way of including it from Module.php?

    • samsonasik said, on May 30, 2013 at 3:05 am

      You can make a viewhelper to do it. for example, you create ServiceManagerCaller view helper like the following :

      namespace Common\View\Helper;
      use Zend\View\Helper\AbstractHelper;
      use \Zend\ServiceManager\ServiceLocatorAwareInterface;
      use \Zend\ServiceManager\ServiceLocatorInterface;
      
      class ServiceManagerCaller extends AbstractHelper
          implements ServiceLocatorAwareInterface
      {
          protected $serviceLocator;
      
          public function setServiceLocator(ServiceLocatorInterface $serviceLocator)
          {
              $this->serviceLocator = $serviceLocator;
          }
      
          public function getServiceLocator()
          {
              return $this->serviceLocator;
          }
      
          public function __invoke()
          {
              //only service_manager key can only call getServiceLocator() directly...
              return $this->getServiceLocator()->getServiceLocator();
          }
      }
      

      then, register at service manager under view_helper :

       'view_helpers' => array(
              'invokables' => array(
                  'ServiceManagerCaller' => 'Common\View\Helper\ServiceManagerCaller',
              ),
      ),
      

      and try to call from view :

      $form = $this->ServiceManagerCaller()->get('YourService');
      
      • Magic said, on May 31, 2013 at 10:48 pm

        Thanks for the reply.

      • samsonasik said, on June 1, 2013 at 12:10 am

        you’re welcome 😉

  13. Ranjan Kumar said, on June 21, 2013 at 12:55 pm

    //User/config/module.config.php
    use Zend\Db\Adapter\Adapter;

    return array(
    ‘factories’ => array(
    ‘IndexController’ => ‘User\Controller\IndexController’,
    ‘UserTable’ => function($sm) {
    $adapter = new Adapter($sm[‘db’]);
    $table = new User\Model\UserTable($adapter);
    return $table;
    }
    ),

    ‘service_manager’=>array(
    ‘factories’ => array(
    ‘UserTable’ => function ($sm) {
    $adapter = $sm->get(‘Zend\Db\Adapter\DbAdapter’);
    $table = new User\Model\UserTable($adapter);
    },
    ),
    ),
    );

    from your blog i tried to use service_manager to get the UserTable(User\Model\UserTable) object from outside the module but it generate fatal error that i have post at last line. while using factories it work nice just use the Zend\Db\Adapter\Adapter. can u suggest how can i get this work done by using service_manager occording to your blog post.

    Fatal error: Call to a member function get() on a non-object in /var/www/zfsparx/module/admin/User/config/module.config.php

  14. tmquang6805 said, on July 17, 2013 at 2:42 pm

    Hi Samsonasik,

    Can you help me to get service manager/locator in Model?

    Thanks a lot

  15. Dennis said, on August 29, 2013 at 4:21 pm

    Just want to say: great list! Helps me a lot to look through the service manager / locator jungle. 😉

  16. Artur Bodera said, on October 14, 2013 at 5:28 pm

    allow_override is a global MM setting.

  17. Lom said, on October 20, 2013 at 9:22 pm

    Hey, hopefully you can help me! I got a bit stucked with the service manager. Currently I want to apply a ServiceFactory for Acl to a view helper (navigation). I have really no glue how to get it workin’ :(. Big big thanks!

    Module.php
    // […]

    public function getServiceConfig()
    {
    return array(
    ‘factories’ => array(
    ‘AclService’ => ‘User\Acl\ServiceFactory’,
    ‘AuthAdapter’ => ‘User\Authentication\UserAuthAdapterFactory’,
    ‘AuthStorage’ => ‘User\Authentication\UserAuthStorageFactory’,
    ‘AuthService’ => ‘User\Authentication\ServiceFactory’,
    // […]
    )
    );
    }

    public function getViewHelperConfig() {
    return array(
    ‘factories’ => array(
    ‘UserIsAllowed’ => ‘User\View\Helper\UserIsAllowedFactory’,
    ‘navigation’ => function(HelperPluginManager $pm) {
    // how to implement a servicefactory right here handled by servicemanager?!
    $acl = new Acl\ServiceFactory(); // not working

    $navigation = $pm->get(‘Zend\View\Helper\Navigation’);
    $navigation->setAcl($acl);
    return $navigation;
    },
    )
    );
    }

    // […]

  18. Lom said, on October 20, 2013 at 9:31 pm

    I think i got it *laugh*, I was a bit stupid :-).

    ‘factories’ => array(
    ‘navigation’ => function(HelperPluginManager $pm) {
    $sm = $pm->getServiceLocator();
    $acl = $sm->get(‘AclService’);
    // …

  19. alan said, on June 12, 2015 at 7:23 pm

    Is that your wife with you in picture?

  20. Alan said, on June 15, 2015 at 5:54 pm

    She is pretty

  21. Anil said, on February 27, 2017 at 2:40 pm

    If you could provide us with Zend Framework 2 examples as a complete file for downloading, then it will be easier for us to learn. As we are finding it difficult in replicating just with the code you are giving in the blog.
    Please look into this, thank you so much for your contribution. 🙂


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: