I'm attempting to convert my beta DI code to the release version of ZF2.
Right now I'm falling over at the very start and there doesn't seem to be any sort of documentation about injecting stuff into the controllers which gets me to think that it's not normal to have dependencies in a controller?
Right now I'm just doing a var_dump('blah');exit; just to try and get some code to run...
I've tried a number of things and right now I expected this to work:
module.config.php
'controllers' => array(
'invokables' => array(
'indexController' => 'Application\Controller\IndexController',
)
)
Module.php
public function getControllerConfig() {
return array(
'factories' => array(
'indexController' => function(ControllerManager $cm) {
var_dump('blah');exit;
},
),
);
}
Right now nothing is happening and it's pretty frustrating... I read something about creating Factories for each Controller but I have 33 and I find that quite insane and stupid... ?
What I'm trying to inject is stuff like a userMapper for grabbing/saving users. So the registerAction creates a user in the database using the userMapper and when you try to login it uses the userMapper to check if there is a user there etc.
The problem here is that 'indexController' is defined as both an invokable and a factory. I think it checks invokables first, so when it finds what it is looking for, it never attempts to run the code in the factory. Just remove the entry in the 'invokables' array.
I just wrote a post on this subject. Instead of creating a separate factory class for each controller, you can do it with closures. If the dependencies are invokable, or easily configured with an options array, it is even easier, all you need is an array listing the classes that can be injected. Check out http://zendblog.shinymayhem.com/2013/09/using-servicemanager-as-inversion-of.html
you can easily do it like this in any Module.php
public function onBootstrap(\Zend\EventManager\EventInterface $e)
{
$serviceManager = $e->getApplication()->getServiceManager();
$myDependency = /*something*/;
$controllerLoader = $serviceManager->get('ControllerLoader');
$controllerLoader->addInitializer(function ($controller) use ($myDependency) {
if (method_exists($instance, 'injectMyDependency')) {
$controller->injectMyDependency($myDependency);
}
});
}
a bit cleaner would to let the controllers which need the dependency implement an interface and check if the controller is an instance of that interface and then set it, not just check if the method exists...
Below is my Initializer code to inject into an arbitrary class.
It was sort of tricky at the beginning to grasp - to automagically inject into controller upon instantiation you have to define initializer in 'initializer' section of 'controllers' section of module.config.php - not in that of 'service_manager' section. Basically to create universal "Aware Interfaces" that will be effective for controllers and the rest - respective initializer keypairs should appear in both sections altogether...
// module/SkeletonClassmapGenerator/Item/ImplementedItem/ImplementedItemInitializer.php
namespace SkeletonClassmapGenerator\Item\ImplementedItem;
use Zend\ServiceManager\InitializerInterface;
use SkeletonClassmapGenerator\Provider\GenericInitializerTrait;
class ImplementedItemInitializer implements InitializerInterface
{
static protected $T_NAMESPACE = __NAMESPACE__;
static protected $T_CLASS = __CLASS__;
use GenericInitializerTrait;
}
Then for the trait (obviously shared among all initializers)...
// module/SkeletonClassmapGenerator/Provider/GenericInitializerTrait.php
namespace SkeletonClassmapGenerator\Provider;
use Zend\ServiceManager\ServiceLocatorInterface;
trait GenericInitializerTrait
{
public function initialize($instance, ServiceLocatorInterface $serviceLocator)
{
if (isset(static::$T_CLASS)&&(isset(static::$T_NAMESPACE))){
$classname = explode('\\', static::$T_CLASS);
$class = end($classname);
preg_match('/([\w]*)Initializer$/i', $class,$matches);
$basename = $matches[1];
if(is_subclass_of($instance,static::$T_NAMESPACE.'\\'.$basename.'AwareInterface')) {
$sl = (method_exists($serviceLocator,'getServiceLocator'))?
$serviceLocator->getServiceLocator():$serviceLocator;
$dependency = $sl->get(static::$T_NAMESPACE.'\\'.$basename.'Interface'); // I use 'Interface' as postfix for Service Manager invokable names
$instance->{'set'.$basename}($dependency);
}
}
}
}
Related
Since version 2.7.0 of zend-mvc the ServiceLocatorAwareInterface is depricated, so are $this->serviceLocator->get() calls inside controllers.
Thats why some days ago I did a huge refactoring of all my modules to inject the needed services/objects through constructors using factories for mostly everything.
Sure, I understand why this is the better/cleaner way to do things, because dependendies are much more visible now. But on the other side:
This leads to a heavy overhead and much more never-used class instances, doesn't it?
Let's look to an example:
Because all my controllers having dependencies, I've created factories for all of them.
CustomerControllerFactory.php
namespace Admin\Factory\Controller;
class CustomerControllerFactory implements FactoryInterface {
public function createService(ServiceLocatorInterface $controllerManager) {
$serviceLocator = $controllerManager->getServiceLocator();
$customerService = $serviceLocator->get('Admin\Service\CustomerService');
$restSyncService = $serviceLocator->get('Admin\Service\SyncRestClientService');
return new \Admin\Controller\CustomerController($customerService, $restSyncService);
}
}
CustomerController.php
namespace Admin\Controller;
class CustomerController extends AbstractRestfulController {
public function __construct($customerService, $restSyncService) {
$this->customerService = $customerService;
$this->restSyncService = $restSyncService;
}
}
module.config.php
'controllers' => [
'factories' => [
'Admin\Controller\CustomerController' => 'Admin\Factory\Controller\CustomerControllerFactory',
]
],
'service_manager' => [
'factories' => [
'Admin\Service\SyncRestClientService' => 'Admin\Factory\SyncRestClientServiceFactory',
]
]
SyncRestClientServiceFactory.php
namespace Admin\Factory;
class SyncRestClientServiceFactory implements FactoryInterface {
public function createService(ServiceLocatorInterface $serviceLocator) {
$entityManager = $serviceLocator->get('doctrine.entitymanager.orm_default');
$x1 = $serviceLocator->get(...);
$x2 = $serviceLocator->get(...);
$x3 = $serviceLocator->get(...);
// ...
return new \Admin\Service\SyncRestClientService($entityManager, $x1, $x2, $x3, ...);
}
}
The SyncRestService is a complex service class which queries some internal server of our system. It has a lot of dependencies, and is always created if a request comes to the CustomerController. But this sync-service is only used inside the syncAction() of the CustomerController! Before I was using simply $this->serviceLocator->get('Admin\Service\SyncRestClientService') inside the syncAction() so only then it was instantiated.
In general it looks like a lot of instances are created through factories at every request, but the most dependencies are not used. Is this an issue because of my design or it is a normal side-effect behaviour of "doing dependency injection through constructors"?
In my opinion it is a normal effect of dependency injection through constructors.
I think you have now two options (not mutually exclusive) to improve how your application works:
Split your controllers, so that the dependencies are instanciated only when needed. This would certainly give rise to more classes, more factories, and so on, but your code would attain more to the single responsability principle
You could use Lazy Services, so that, even if some services are dependencies of the whole controller, they will be actually instanciated only the first time they are called (so never for the actions where they are not called!)
If you only use your SyncRestClientService inside a controller you should consider changing it from a service to a controller plugin (or make a controller plugin where you inject your SyncRestClientService).
Like that you can still get it inside your controller syncAction method very similar to like you did before. This is exactly the purpose of the ZF2 controller plugins.
First you need to create your controller plugin class (extending Zend\Mvc\Controller\Plugin\AbstractPlugin):
<?php
namespace Application\Controller\Plugin;
use Zend\Mvc\Controller\Plugin\AbstractPlugin;
class SyncPlugin extends AbstractPlugin{
protected $syncRestClientService;
public function __constuct(SyncRestClientService $syncRestClientService){
$this->syncRestClientService = $syncRestClientService
}
public function sync(){
// do your syncing using the service that was injected
}
}
Then a factory to inject your service in the class:
<?php
namespace Application\Controller\Plugin\Factory;
use Application\Controller\Plugin\SyncPlugin;
class SyncPluginFactory implements FactoryInterface
{
/**
* #param ServiceLocatorInterface $serviceController
* #return SyncPlugin
*/
public function createService(ServiceLocatorInterface $serviceController)
{
$serviceManager = $serviceController->getServiceLocator();
$syncRestClientService = $serviceManager>get('Admin\Service\SyncRestClientService');
return new SyncPlugin($syncRestClientService);
}
}
Then you need to register your plugin in your module.config.php:
<?php
return array(
//...
'controller_plugins' => array(
'factories' => array(
'SyncPlugin' => 'Application\Controller\Plugin\Factory\SyncPluginFactory',
)
),
// ...
);
Now you can use it inside your controller action like this:
protected function syncAction(){
$plugin = $this->plugin('SyncPlugin');
//now you can call your sync logic using the plugin
$plugin->sync();
}
Read more on controller plugins here in the documentation
Maybe you only need one dependency to be injected into the controller constructor (the ServiceManager instance). I don't see any cops around...
namespace Admin\Factory\Controller;
class CustomerControllerFactory implements FactoryInterface {
public function createService(ServiceLocatorInterface $controllerManager)
{
$serviceLocator = $controllerManager->getServiceLocator();
return new \Admin\Controller\CustomerController($serviceLocator);
}
}
Personally I get the action name in the controller factory to inject services on a per action basis.
Have a look at my sites controller.
namespace Admin\Controller\Service;
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
use Admin\Controller\SitesController;
use Admin\Model\Sites as Models;
class SitesControllerFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $serviceLocator)
{
$actionName = $serviceLocator->getServiceLocator()->get('Application')->getMvcEvent()->getRouteMatch()->getParam('action');
$controller = new SitesController();
switch ($actionName) {
case 'list':
$controller->setModel($serviceLocator->getServiceLocator()->get(Models\ListSitesModel::class));
break;
case 'view':
$controller->setModel($serviceLocator->getServiceLocator()->get(Models\ViewSiteModel::class));
break;
case 'add':
$controller->setModel($serviceLocator->getServiceLocator()->get(Models\AddSiteModel::class));
break;
case 'edit':
$controller->setModel($serviceLocator->getServiceLocator()->get(Models\EditSiteModel::class));
break;
}
return $controller;
}
}
As you can see I use $serviceLocator->getServiceLocator()->get('Application')->getMvcEvent()->getRouteMatch()->getParam('action'); to get the action name and use a switch statement to inject the dependencies when required.
I don't know if this is the best solution but it works for me.
Hope this helps.
I need to write a view helper that gets a service and do something with it. I successfully implemented the view helper to have access to the service locator. The problem is that the service I want to get is not being found through the service locator when the __invoke method is called.
The view helper code:
<?php
namespace Application\View\Helper;
use Zend\View\Helper\AbstractHelper,
Zend\ServiceManager\ServiceLocatorAwareInterface,
Application\Model;
class LoggedCustomer extends AbstractHelper implements ServiceLocatorAwareInterface
{
use \Zend\ServiceManager\ServiceLocatorAwareTrait;
public function __invoke()
{
$model = new Model\Customer($this->getServiceLocator());
return $model->getCurrent();
}
}
A snippet of the model code:
namespace Application\Model;
use Application\Entity,
Andreatta\Model\Base as Base;
class Customer extends Base
{
/**
*
* #return Zend\Authentication\AuthenticationService
*/
public function getAuthService()
{
$serviceLocator = $this->getServiceLocator();
return $serviceLocator->get('Application\Auth');
}
/**
*
* #return Zend\Authentication\Adapter\AdapterInterface
*/
protected function getAuthAdapter()
{
return $this->getAuthService()->getAdapter();
}
public function getCurrent()
{
$authService = $this->getAuthService();
if ($authService->hasIdentity())
return $authService->getIdentity();
return null;
}
The snippet from module.config.php:
'service_manager' => array
(
'factories' => array
(
'Application\Auth' => function($sm)
{
$authService = $sm->get('doctrine.authenticationservice.application');
$authService->setStorage( new \Zend\Authentication\Storage\Session('Application\Auth'));
return $authService;
},
),
),
'view_helpers' => array
(
'invokables' => array
(
'loggedCustomer' => 'Application\View\Helper\LoggedCustomer',
),
),
When calling the view helper from any view I get the following:
Zend\View\HelperPluginManager::get was unable to fetch or create an instance for Application\Auth
The weird is that the application is functioning correctly (i.e. this service is being normally used by other parts of the application).
EDIT:
I did some research and I think the only services that I can access through the service manager inside the view helper are the ones registered inside the 'view_manager' section of module.config.php. Does anyone have an idea of how to access the other services?
$this->getServiceLocator() in view helper can only get u other view helpers you need to use $this->getServiceLocator()->getServiceLocator() to get the application services
#rafaame: I find a simple way to access service locator in view Helper
We just use:
$this->getView()->getHelperPluginManager()->getServiceLocator();
to get a service locator
A sample view Helper:
namespace Tmcore\View\Helper;
use Zend\View\Helper\AbstractHelper;
class Resource extends AbstractHelper
{
public function adminResource()
{
$sm = $this->getView()->getHelperPluginManager()->getServiceLocator();
$adminConfig = $sm->get('ModuleManager')->loadModule('admin')->getConfig();
return $adminConfig;
}
}
I guess you are retrieving the Zend\View\HelperPluginManager instead of the correct ServiceManager.
Probably you are not injecting it as you should.
That makes sense if thats your complete LoggedCustomer code, since you are not saving the SM. As far as I know, if you implement the ServiceLocatorAwareInterface the SM will be injected, but you have to handle it.
UPDATE:
sorry, i didnt realize you had ServiceLocatorAwareTrait; thats the same.
But, reading http://framework.zend.com/manual/2.0/en/modules/zend.service-manager.quick-start.html
i see
By default, the Zend Framework MVC registers an initializer that will inject the ServiceManager instance, which is an implementation of
Zend\ServiceManager\ServiceLocatorInterface, into any class
implementing Zend\ServiceManager\ServiceLocatorAwareInterface. A
simple implementation looks like the following.
So, the service manager is only being injected ... if you implement ServiceLocatorAwareInterface in a controller.
So, you should manually inject the service manager.
for that, what i use to do is to create a factory in Module.php, instead of creating the invokable in the config. for that you implement this function:
public function getViewHelperConfig()
{
return array(
'factories' => array(
'loggedCustomer' => function($sm) {
$vh = new View\Helper\LoggedCustomer();
$vh->setServiceLocator($sm->getServiceLocator());
return $vh;
}
);
}
Also, i wont have the view helper implementing ServiceLocatorAwareInterface, so nothing else is automaticaly injected.
And with this it will work
It appears that the service manager that is injected into the view helper has only the services that are registered within the section 'view_manager' of module configs.
It is possible to inject the "main" service manager by registering the view helper as a factory like this:
'view_helpers' =>
[
'factories' =>
[
'loggedCustomer' => function($pluginManager)
{
$serviceLocator = $pluginManager->getServiceLocator();
$viewHelper = new View\Helper\LoggedCustomer();
$viewHelper->setServiceLocator($serviceLocator);
return $viewHelper;
},
]
],
But you have to make sure that you treat it in setServiceLocator method in the view helper. Otherwise the "limited" service manager will be injected into the view helper later on. Like this:
public function setServiceLocator(ServiceLocatorInterface $serviceLocator)
{
if($this->serviceLocator !== null)
return $this;
$this->serviceLocator = $serviceLocator;
return $this;
}
It fixes the problem, but it appears to be a tremendous hack to me.
In view helpers, if you want to access application services then use
$this->getServiceLocator()->getServiceLocator()
I've made a new module in my ZF2 application that has a model and a mapper. In the Module.php file I have created a factory for the AbstractDbMapper as follows:
public function getServiceConfig()
{
return array(
'factories' => array(
'ZfcProductMapper' => function ($sm) {
$mapper = new ProductMapper();
$mapper->setDbAdapter($sm->get('ZendDbAdapterAdapter'));
$mapper->setEntityPrototype(new Product());
$mapper->setHydrator(new **????**);
return $mapper;
}
),
);
}
I currently have my hydrator in the Product class as 2 functions 'extract' and 'hydrate'. My question is, how can I set the hydrator in my factory as being in the Product class and not as a whole new class?
Also, when are these hydrator functions called? At the moment I'm calling them myself when I need an array and not an object (for JSON for example) so what I'm really wondering is why does the factory need to know about these hydrators since only I know when they are needed.
I'm trying to make the Zend\ServiceManager use Zend\Di to create my instances, since I have pre-scanned and cached DI definitions already. I realize this might come with a speed penalty but on the other hand, I need to write a lot less meta-code.
The ServiceManager documentation says that
the ServiceManager also provides optional ties to Zend\Di, allowing Di
to act as an initializer or an abstract factory for the manager.
But I don't find any examples of how make the ServiceManager use Zend\Di. I'm not even sure where I should set this up, maybe in Module::getServiceConfig()? Can anyone provide some example code?
The following works for me. In order to make Zend\Di compatible with Zend\ServiceManager, I extended a class MyLib\Di\Di from Zend\Di\Di which implements the AbstractFactoryInterface.
namespace MyLib\Di;
use Zend\ServiceManager\AbstractFactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
class Di extends \Zend\Di\Di implements AbstractFactoryInterface
{
public function canCreateServiceWithName(ServiceLocatorInterface $serviceLocator, $name, $requestedName)
{
return true;
}
public function createServiceWithName(ServiceLocatorInterface $serviceLocator, $name, $requestedName)
{
return $this->get($requestedName);
}
}
Now, I can use MyLib\Di\Di as a fallback abstract factory for Zend\ServiceManager. Here's an example of how I create my IndexController. The IndexController's dependencies (constructor parameters) are injected automatically.
class Module
{
...
public function getServiceConfig()
{
$this->di = new \MyLib\Di\Di;
$this->configureDi($this->di); // Set up definitions and shared instances
return array(
'abstract_factories' => array($this->di),
);
}
public function getControllerConfig()
{
return array(
'factories' => array(
'Survey\Controller\IndexController' => function() {
return $this->di->get('Survey\Controller\IndexController');
},
),
);
}
}
One option - add to config/module.config.php
'service_manager' => array(
'invokables' => array(
'Application\Service\User' => 'Application\Service\User',
),
),
then class needs to implement Zend\ServiceManager\ServiceManagerAwareInterface
When initiated, serviceManager instance is going to be injected, then you can use something like this in class:
$authService = $this->getServiceManager()->get('Zend\Authentication\AuthenticationService');
second option would be to put it into Module.php
public function getServiceConfig()
EDIT: a few weeks after I posted this question Evan Coury wrote an excellent blog post on the topic of the ZF2 ServiceManager, which is where I found the best answers to my questions: http://blog.evan.pro/introduction-to-the-zend-framework-2-servicemanager
--
I'm working on a project using ZendFramework 2.0.0beta4 and am having trouble using the Zend\ServiceManager to handle dependencies. Here is the current ZF2 ServiceManager documentation
It lists 6 sub-keys to use when registering classes with the ServiceManager for use in our modules: abstract_factories, aliases, factories, invokables, services, and shared. If I just want to register a model class which I'm going to use in my controller to pull data from a database, which one is best? I'm specifically trying to adapt an example from the ZF2 Skeleton Application shown below to my own application (DashboardTable is a model), and this example uses the factories way.
public function getServiceConfiguration()
{
return array(
'factories' => array(
'album-table' => function($sm) {
$dbAdapter = $sm->get('db-adapter');
$table = new DashboardTable($dbAdapter);
return $table;
},
'test-model' => Dashboard\Model\TestModel(),
),
);
}
However, I don't know how 'db-adapter' is getting into the ServiceManager ($sm) in my separate working example from the SkeletonApplication - it has to do with an entry in the autoloaded global.php config file which has a 'db' entry containing the DB info. Because I don't know exactly how that's getting from the config file to ServiceManager, I created the simple entry below that to reduce the problem to its base components - "test-model". When I comment out the 'dashboard-table' entry and call a function from TestModel in my controller which simply outputs some text. Below is the ServiceManager config from my Module.php
<?php
namespace Dashboard\Model;
class TestModel {
public function testMethod()
{
$testResult = "Hello";
return $testResult;
}
}
Which is then passed from my controller to the view:
<?php
namespace Dashboard\Controller;
use Zend\Mvc\Controller\ActionController;
use Zend\View\Model\ViewModel;
use Dashboard\Model\AlbumTable;
use Dashboard\Model\TestModel;
use Dashboard\Model\Dashboard;
class DashboardController extends ActionController
{
public function indexAction()
{
return new ViewModel(array(
'users' => $this->getTestModel()->testMethod(),
));
}
public function getAlbumTable()
{
if (!$this->albumTable) {
$sm = $this->getServiceLocator();
$this->albumTable = $sm->get('album-table');
}
return $this->albumTable;
}
public function getTestModel()
{
if (!$this->testModel) {
$sm = $this->getServiceLocator();
$this->testModel = $sm->get('test-model');
}
return $this->testModel;
}
}
This code gives me a completely blank page, no errors. When I comment out the ServiceManager config from Module.php and just render a new ViewModel without any passing any arguments in my DashboardController.php file the page renders normally - loading layout.phtml and index.phtml.
I believe I'm misunderstanding a fundamental piece of how to use the ServiceManager or possible ZF2 in general, and will greatly appreciate any insight anybody can give. This is also my first question on StackOverflow so I welcome any advice on formatting my question. Thanks.
There are two good options to get factories from service managers. One is the creation of factory classes, which happens most time in the Zend Framework code itself. The second one is using closures, as you are doing.
Make sure you do not type things like:
'test-model' => Dashboard\Model\TestModel(),
But a real closure like your first one is a good example. Secondly, the Service Manager always gives an exception when you try to get a service which fails to instantiate. Note this exception does not include the message why: the class might not be found or an exception is thrown during instantiation (for example because the service manager cannot instantiate a dependency of the service you are trying to get).
A last remark is you do not need to import FQCN (fully qualified class names) with use statements at the location you are trying to get. But you need to import the FQCNs when you are trying to instantiate.
So this works:
<?php
class MyClass
{
protected $sm;
public function setServiceManager($sm)
{
$this->sm = $sm;
}
public function doSomething()
{
$this->sm->get('some-special-key');
}
}
And this too:
<?php
use Foo\Bar\Baz;
$serviceConfig = array(
'factories' => array(
'some-special-key' => function($sm) {
return new Baz;
}
),
);
But this not (if you try to get a Foo\Bar\Baz):
<?php
$serviceConfig = array(
'factories' => array(
'some-special-key' => function($sm) {
return new Baz;
}
),
);
You might want to checkout my SlmCmfKernel repository. In my Module.php I include a service configuration file, which is put in a separate location. In another part of the code I get a service from the manager.
Just to clarify:
public function getServiceConfiguration()
{
return array(
'factories' => array(
'test-model' => function($sm){
return new Model\TestModel;
},
),
);
}
Can also be written as an invokable:
public function getServiceConfiguration()
{
return array(
'invokables' => array(
'test-model' => 'Model\TestModel',
),
);
}
In that case, you might want to consider having it defined in a config file instead of Module.php, as you'd be able to take advantage of config caching since it's simply an array of scalars.
I ended up finding the answer to my own question through more debugging (I previously hadn't had ini_set('display_errors', '1'); set - silly me).
The proper syntax to add a class to the ZF2 ServiceManager (within your Module.php) appears to be:
public function getServiceConfiguration()
{
return array(
'factories' => array(
'album-table' => function($sm) {
$dbAdapter = $sm->get('db-adapter');
$table = new AlbumTable($dbAdapter);
return $table;
},
'test-model' => function($sm){
return new Model\TestModel;
},
),
);
}
And just for completeness, in order to call a method from the class you're including you can use this in your controller file (DashboardController.php in my case) as long as you're extending the ActionController class:
class DashboardController extends ActionController
{
public function indexAction()
{
return new ViewModel(array(
'users' => $this->getTestModel()->testMethod(),
));
}
public function getTestModel()
{
if (!$this->testModel) {
$sm = $this->getServiceLocator();
$this->testModel = $sm->get('test-model');
}
return $this->testModel;
}
}
Where testMethod() is a method from within the TestModel class. Some notes from this for anybody new to Zend or namespaces - one of my issues was that I was using the full class name reference (Dashboard\Model\TestModel) when I had set the namespace at the top of the file to Dashboard, so the first Dashboard was unnecessary and caused PHP to look for Dashboard\Dashboard\Model\TestModel. Also, as of this writing sample ZF2 module are scarce - I recommend looking to samples like ZfcUser by EvanDotPro for examples on this type of thing.
My original confusion about the various sub-keys for adding classes to the ServiceManager still lingers though, so if you have any insight as to that I will continue to monitor this question and will mark your answer as "the" answer should you solve that bit, thank you :).