Why is the Servicemanager injected in a invokable and not a service - php

I'm currently playing with the ZF2 serviceManager, and i'm trying to figure out why the serviceManager doesn't inject the sm in a class that implements ServiceLocatorAwareInterface.
My main question is am i doing it right or is the "services" key not for services that implement ServiceLocatorAwareInterface but for services that don't need injection?
in Module.php
public function getServiceConfig() {
return array(
'invokables' => array(
'myService1' => 'MyModule\Service\Service'
),
'services' => array(
'myService2' => new MyModule\Service\Service(),
),
);
}
in MyModule\Service\Service.php
namespace MyModule\Service;
use Zend\ServiceManager\ServiceManagerAwareInterface;
use Zend\ServiceManager\ServiceManager;
class Service implements ServiceManagerAwareInterface
{
/**
* #var ServiceManager
*/
protected $serviceManager = NULL;
/**
* Retrieve service manager instance
*
* #return ServiceManager
*/
public function getServiceManager()
{
return $this->serviceManager;
}
/**
* Set service manager instance
*
* #param ServiceManager $serviceManager
*/
public function setServiceManager(ServiceManager $serviceManager)
{
$this->serviceManager = $serviceManager;
}
}
When i call the service in a controller
<?php
namespace MyModule\Controller;
use Zend\Mvc\Controller\AbstractActionController;
class IndexController extends AbstractActionController
{
public function IndexAction() {
$service1 = $this->getServiceLocator()->get('myService1');
$sm1 = $service1->getServiceManager();
//$sm1 becomes a object of Zend\ServiceManager\ServiceManager
//In other words i now can access the SM from within my service.
$service2 = $this->getServiceLocator()->get('myService2');
$sm2 = $service2->getServiceManager();
//$sm2 becomes NULL
//The service isn't aware of the SM and can't access it.
}
}

You should continue to use the "invokables" section if you'd like to utilize the ServiceManagerAwareInterface and the automatic injection of the Service Manager into your service.
Looking through /Zend/ServiceManager/ServiceManager.php, "services" are meant to be registered as already instantiated objects with the ServiceManager. When the service locator looks up in it's local cache of services during retrieval, it assumes that "services" are already fully set up and does not inject the sm or run any initializers.
"invokables", "factories", "abstract_factories" are created on the fly and injects the sm when the "initializers" are run on a newly created service instance (see function create($name)).

Forget about getServiceConfig() and use your module config instead. It's faster and cacheable:
module.config.php:
'service_manager' => array(
'invokables' => array(
'MyModule\Service\Service' => 'MyModule\Service\Service',
),
)
MyModule\Service\Service.php:
<?php
namespace MyModule\Service;
use \Zend\ServiceManager\ServiceLocatorInterface;
class Service
{
public function __invoke(ServiceLocatorInterface $sm)
{
// go bananas here :)
}
public functiom greet()
{
return 'Hello World';
}
}
anywhere inside zend2:
<?php
$service=$serviceManager->get('MyModule\Service\Service');
echo $service->greet();

Related

how can I pass a variable to partial navigation script in zf2?

I've made a global variable in bootstrap of Module.php
public function setCashServiceToView($event) {
$app = $event->getParam('application');
$cashService = $app->getServiceManager()->get('Calculator/Service/CashServiceInterface');
$viewModel = $event->getViewModel();
$viewModel->setVariables(array(
'cashService' => $cashService,
));
}
public function onBootstrap($e) {
$app = $e->getParam('application');
$app->getEventManager()->attach(\Zend\Mvc\MvcEvent::EVENT_RENDER, array($this, 'setCashServiceToView'), 100);
}
I can use it inside of my layout.phtml as
$this->cashService;
But I need this variable to use in my partial script of navigation menu, which I call in layout.phtml:
echo $this->navigation('navigation')
->menu()->setPartial('partial/menu')
->render();
?>
How can I use it inside of my partial/menu.phtml? And may be there is a better way, than to declare it in onBootstrap function?
Thank you for your answers. I decided to make an extended class of \Zend\View\Helper\Navigation\Menu to provide there a property of cashService. However I receive an error:'Zend\View\Helper\Navigation\PluginManager::get was unable to fetch or create an instance for Calculator\Service\CashServiceInterface'.
I need this service to display navigation menu. Seems weird, but that's true. I display some diagram in it, using the data, which I get from the service. So why do I have the error?
I added to module.config.php
'navigation_helpers' => array(
'factories' => array(
'mainMenu' => 'Calculator\View\Helper\Factory\MainMenuFactory'
),
MainMenuFactory:
namespace Calculator\View\Helper\Factory;
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
use Calculator\View\Helper\Model\MainMenu;
Class MainMenuFactory implements FactoryInterface {
/**
* Create service
*
* #param ServiceLocatorInterface $serviceLocator
* #return mixed
*/
public function createService(ServiceLocatorInterface $serviceLocator) {
return new MainMenu(
$serviceLocator->get('Calculator\Service\CashServiceInterface')
);
}
P.S: CashServiceInterface is an alias to CashServiceFactory
You could remove the event listener and use a custom view helper to access the service in the view.
namespace Calculator\View\Helper;
use Zend\View\Helper\AbstractHelper;
class CashService extends AbstractHelper
{
protected $cashService;
public function __construct(CashServiceInterface $cashService)
{
$this->cashService = $cashService;
}
public function __invoke()
{
return $this->cashService;
}
}
Create a factory.
namespace Calculator\View\Helper;
class CashServiceFactory
{
public function __invoke($viewPluginManager)
{
$serviceManager = $viewPluginManager->getServiceLocator();
$cashService = $serviceManager->get('Calculator\\Service\\CashServiceInterface');
return new CashService($cashService);
}
}
Register the new helper in moudle.config.php.
'view_helpers' => [
'factories' => [
'CashService' => 'Calculator\View\Helper\CashServiceFactory',
],
],
Then you can use the plugin in all view scripts.
$cashService = $this->cashService();

ZF2 Getting Autoloaded config info in a custom class

I have been racking my brain now for the better part of two days. I'm using Zend Apigility to create a RESTful web API application. Apigility builds its application using ZF2.
I created a custom class that I use throughout my API.
I would like to read in some autoloaded configuration information to make a connection to an memcache server. The file that is being autoloaded into the service manager is:
memcache.config.local.php:
return array(
'memcache' => array(
'server' => '10.70.2.86',
'port' => '11211',
),
);
My custom class that my REST services are calling is called checkAuth:
checkAuth.php:
namespace equiAuth\V1\Rest\AuthTools;
use Zend\ServiceManager\ServiceLocatorAwareInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
class checkAuth implements ServiceLocatorAwareInterface{
protected $services;
public function setServiceLocator(ServiceLocatorInterface $serviceLocator)
{
$this->services = $serviceLocator;
}
public function getServiceLocator()
{
return $this->services;
}
public function userAuths() {
//** Some Code
$config = $this->getServiceLocator()->get('config');
// **
}
}
I believe I'm injecting the service manager into the class from my module.config.php with the following code:
'service_manager' => array(
'invokables' => array(
'checkAuth' => 'equiAuth\V1\Rest\AuthTools\checkAuth',
),
),
When I hit the code when I'm trying to read the 'config' from the get method of the ServiceLocator I get the following error:
Fatal error: Call to a member function get() on a non-object
I know I'm missing something, but I cant for the life of me figure out what.
Give your class an API that allow's you to 'set' the configuration from client code. This could be via the constructor or
a public setter.
namespace equiAuth\V1\Rest\AuthTools;
class CheckAuth
{
protected $config;
public function __construct(array $config = array())
{
$this->setConfig($config);
}
public function setConfig(array $config)
{
$this->config = $config;
}
public function doStuff()
{
$server = $this->config['server'];
}
}
In order to 'set' the configuration you would also need to also create a service factory class. The idea in the factory is to give you an area to inject the configuration in to the service; with the updates to CheckAuth above we can now do so very easily.
namespace equiAuth\V1\Rest\AuthTools;
use equiAuth\V1\Rest\AuthTools\CheckAuth;
use Zend\ServiceManager\ServiceLocatorInterface;
use Zend\ServiceManager\FactoryInterface;
class CheckAuthFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $serviceLocator)
{
$config = $serviceLocator->get('config');
return new CheckAuth($config['memcache']);
}
}
Lastly, change the registered service with the service manager; the change here is service key form invokables to factories as we need to register the
above factory to create it.
// module.config.php
'service_manager' => array(
'factories' => array(
'checkAuth' => 'equiAuth\V1\Rest\AuthTools\CheckAuthFactory',
),
),
ZF2 use ServiceManager Container as well.
Your code is right at all, but
To auto-inject the servicelocator on your class you just need to use
$checkAuth = $this->getServiceLocator()->get('checkAuth');
then you can call
$checkAuth->userAuths();
and should work.
If you try to use:
$checkAuth = new \equiAuth\V1\Rest\AuthTools\checkAuth();
$checkAuth->userAuths(); //error
Will not work because what inject the serviceLocator into your class is just the
ServiceManager, once you use serviceManager you need to be evangelist with them.
But if you try:
$checkAuth = new \equiAuth\V1\Rest\AuthTools\checkAuth();
$checkAuth->setServiceLocator($serviceLocator)
//get $serviceLocator from ServiceManager Container
$checkAuth->userAuths();
Will work too.
Good job!

ZF2 service locator interface does not return an object

I am trying to create a model that will have access to the ZF2 service locator.
I have a model class that looks like this:
use Zend\ServiceManager\ServiceLocatorAwareInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
class testDelete implements ServiceLocatorAwareInterface
{
protected $services;
/**
* construct function
*/
public function __construct ()
{
$router = $this->getServiceLocator()->get('Router');
}
public function setServiceLocator(ServiceLocatorInterface $serviceLocator)
{
$this->services = $serviceLocator;
}
public function getServiceLocator()
{
return $this->services;
}
}
In reading the tutorials and blogs this should give me an instance of the service locator, which I can then use to call classes. But when I call it I get a message
Fatal error: Call to a member function get() on a non-object...
Does anyone know why this would be?
Do I need to some sort of setup to get the service locator interface to work?
Because your class implements ServiceLocatorAware, the service manager will automatically inject the service locator into it. However, it can only do that if the service manager is the thing instantiating the testDelete class. So you need to setup a service for testDelete.
Once you've done that, you still won't be able to call $this->getServiceLocator() from __construct(), as the dependency won't have been injected into the class yet.
If all you want is to get the router into your testDelete class, just create a service for testDelete and pass the router in as a dependency. This would be much easier than what you're currently trying to do.
It looks like you need to set the "Router" service in the getServiceConfig() method of your Modules Module.php file:
public function getServiceConfig()
{
return array
(
'factories' => array
(
'Router' => function($serviceManager)
{
... your logic ...
return $Router
},
),
);
}
Your Module.php file will look something like this:
namespace ModuleName;
use Zend\Mvc\ModuleRouteListener;
use Zend\Mvc\MvcEvent;
use Zend\Db\ResultSet\ResultSet;
use Zend\Db\TableGateway\TableGateway;
use Zend\ModuleManager\Feature\ServiceProviderInterface;
use Zend\ModuleManager\Feature\AutoloaderProviderInterface;
/**
* Tables (& Databases) Used in this Module
*/
use Your\Model\ModelName;
use Your\Mapper\ModelMapperName;
class Module implements AutoloaderProviderInterface
{
public function onBootstrap(MvcEvent $e)
{
$eventManager = $e->getApplication()->getEventManager();
$serviceManager = $e->getApplication()->getServiceManager();
$sharedManager = $e->getApplication()->getEventManager()->getSharedManager();
$moduleRouteListener = new ModuleRouteListener();
$moduleRouteListener->attach($eventManager);
/**
* Additional logic for Setting up Logging, Pre-Initializations, Exceptions etc
*/
}
public function getConfig()
{
return include __DIR__ . '/config/module.config.php';
}
public function getAutoloaderConfig()
{
return array
(
'Zend\Loader\ClassMapAutoloader' => array
(
include __DIR__ . '/autoload_classmap.php',
),
'Zend\Loader\StandardAutoloader' => array
(
'namespaces' => array
(
__NAMESPACE__ => __DIR__ . '/src/' . __NAMESPACE__,
),
),
);
}
public function getServiceConfig()
{
return array
(
'factories' => array
(
'Router' => function($serviceManager)
{
... your logic ...
return $Router
},
),
);
}
}

Zend Framework 2 Service in View Helper

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()

Zend framework 2 translator in model

How to get translator in model?
Inside view we can get translator using this code
$this->translate('Text')
Inside controller we can get translator using this code
$translator=$this->getServiceLocator()->get('translator');
$translator->translate("Text") ;
But how to get translator in model?
I'd tried so many ways to get service locator in models
2 of those
1)Using MVC events
$e=new MvcEvent();
$sm=$e->getApplication()->getServiceManager();
$this->translator = $sm->get('translator');
if i pring $sm it is showing null. but it works fine in Model.php onBootstrap
2)Created one model which implements ServiceLocatorAwareInterface
SomeModel.php
<?php
namespace Web\Model;
use Zend\ServiceManager\ServiceLocatorAwareInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
class SomeModel implements ServiceLocatorAwareInterface
{
protected $services;
public function setServiceLocator(ServiceLocatorInterface $locator)
{
$this->services = $locator;
}
public function getServiceLocator()
{
return $this->services;
}
}
and used that inside my model
$sl = new SomeModel();
$sm=$sl->getServiceManager();
var_dump($sm); exit;
$this->translator = $sm->get('translator');
this is also printing null.
If you don't need the servicemanager instance in your model, simply inject translator instance to it.
For example:
// Your model's constructor
class MyModel {
// Use the trait if your php version >= 5.4.0
use \Zend\I18n\Translator\TranslatorAwareTrait;
public function __construct( $translator )
{
$this->setTranslator( $translator );
}
public function modelMethodWhichNeedsToUseTranslator()
{
// ...
$text = $this->getTranslator()->translate('lorem ipsum');
// ...
}
}
When you creating your model first time on service or controller level
class someClass implements ServiceLocatorAwareInterface {
public function theMethodWhichCreatesYourModelInstance()
{
// ...
$sm = $this->getServiceLocator();
$model = new \Namespace\MyModel( $sm->get('translator') )
// ...
}
}
If you need to instantiate your model (new MyModel();) on multiple methods/classes, consider to writing a factory for it.
Here is a nice article about Dependency Injection and PHP by Ralph Schindler for more detailed comments about this approach.
For your Model class to be ServiceLocatorAware, you not only need to implement the interface, you also need to make your model a service of the service manager, and fetch the model from there.
Add your model to the service manager, since it doesn't appear to need any constructor params, it's invokable, so you can add it to the invokables array in service manager config. You can do that by using the getServiceConfig() method in your Module class...
class Module
{
public function getServiceConfig()
{
return array(
'invokables' => array(
'SomeModel' => 'Fully\Qualified\ClassName\To\SomeModel',
),
);
}
}
Then, instead of calling the new keyword to create your model instance, you fetch it from the service manager, for instance, by calling the getServiceLocator() method in a controller action...
public function fooAction()
{
$sm = $this->getServiceLocator();
$model = $sm->get('SomeModel');
}
When your model is fetched from the service manager, a service initializer will look to see if it implements the ServiceLocatorAwareInterface and automatically call setServiceLocator() if it does, passing it an instance of the service manager.

Categories