I am trying to access service locator object inside my controller but unable to do this.
I tried online help but most of them are following approach for ZF2
Previously Servicelocator access in zf2 was a breeze, I just had to do $this->getServiceLocator();
I have tried creating factory Class for my controller and Created createService method there but it says I have to implement __invoke() method too.
My Objective is to do something like this
public function getPropertyTable()
{
if (!$this->PropertyTable) {
$sm = $this->getServiceLocator();
$this->PropertyTable = $sm->get('Application\Model\PropertyTable');
}
return $this->PropertyTable;
}
Can anyone provide me a complete steps to achieve this?
I have tried to implement almost all Answers related to Servicelocator before asking this question, so please help me before marking this as Duplicate or something, ignore the typos
Thanks everyone to tell me I am doing it the wrong way. Some more research on this topic helped me to get my issue resolved here is what I have done to solve it
Create Factory class for your Controller
You have to create a factory class for your controller which will implement FactoryInterface of zend.
in there you have to call
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
return new ListController($container->get(PropertyInterface::class));
}
here I am passing PropertyInterface refrence which is implemented by my another table called Property Table in which I have given body to all my inteface functions for model
like searchProperty()
Add factory class in config file module.config.php for your controller
instructing config file to let our factory create the object for our controller
'controllers' => [
'factories' => [
// Update the following line:
Controller\ListController::class => Factory\ListControllerFactory::class,
],
],
Register your Model in config file
You have to add new section for service manager and provide your model classes there.
// Add this section:
'service_manager' => [
'aliases' => [
Model\PropertyInterface::class => Model\PropertyTable::class,
],
'factories' => [
Model\PropertyTable::class => InvokableFactory::class,
],
],
Now only thing left is add functions in your PropertyInterface and Implentation in PropertyTable for them and then call them in your controller
This Complete Steps for implementation Helped me in implementing the new flow.
Thanks to community. You all are best.
As long as the new factory interface in ZF3 is:
interface FactoryInterface
{
/**
* Create an object
*
* #param ContainerInterface $container
* #param string $requestedName
* #param null|array $options
* #return object
*/
public function __invoke(ContainerInterface $container, $requestedName, array $options = null);
}
you have to implements your factory like this.
For helping you, you can use that link
EDIT :
In practise you should have this :
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
$propertyTable = $container->get('Application\Model\PropertyTable');
return new Controller($propertyTable);
}
Another way
/**
* Retrieve service manager instance
* #throws \Psr\Container\ContainerExceptionInterface
* #throws \Psr\Container\NotFoundExceptionInterface
* #return ContainerInterface
*/
public function getServiceLocator()
{
return $this->getEvent()->getApplication()->getServiceManager();
}
In ZF3 it is not recommended to pass service locator into controller.
You need to get all your dependencies from $container inside factory of the controller and pass they into the controller through the constructor (or setters).
Related
I know there are dozen of questions about PHPRenderer not finding the path of a template, but I think the problem is quite different here.
First, the goal is to render a view to a variable in order to send it to a PDF Renderer (I use ZF3 TCPDF module). If there is any better way to do that, please tell me.
Here is roughly the architecture of the project: https://imgur.com/UhQ7hgP
In AlertAction() of ToolsController, I return the view like this, and it works, which make me think the template path is alright.
$view = new ViewModel();
$view->setTemplate('tools/tools/alert');
return $view;
However, when I try to render the same view with the same path in exportPDFAction(), it does not work and gives the following error.
Zend\View\Renderer\PhpRenderer::render: Unable to render template "tools/tools/alert"; resolver could not resolve to a file
The code in exportPDFAction() is:
$view = new ViewModel();
$renderer = new PhpRenderer();
$view->setTemplate('tools/tools/alert');
$html = $renderer->render($view);
I assume the last line screws it as it is the difference, but I can't get why, does anyone have any clue ?
Quite all the topics about Template path on SO were talking about the template map in module.config.php, but I think this is not the problem here since it works perfectly in AlertAction().
EDIT
The PhpRenderer is injected in the controller directly in module.config.php:
'controllers' => [
'factories' => [
ToolsController::class => function($container) {
return new ToolsController(
$container->get(Adapter::class),
$container->get(\TCPDF::class),
$container->get(PhpRenderer::class)
);
},
],
],
EDIT 2
This is the controller constructor:
public function __construct($db, $tcpdf, $renderer)
{
$this->db = $db;
$this->tcpdf = $tcpdf;
$this->renderer = $renderer;
...
}
The error you're getting might be due to the fact your Renderer is not injected via the Factory.
Try:
class MyCustomControllerFactory implements FactoryInterface
{
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
/** #var \Zend\View\Renderer\PhpRenderer $renderer */
$renderer = $container->get('ViewRenderer')
return new MyCustomController($renderer);
}
}
In the Controller, require it be set in the __construct() function:
public function __construct(PhpRenderer $renderer)
{
// ... set it somewhere, e.g.:
$this->setRenderer($renderer);
}
Then use it in your function:
$view = new ViewModel();
$renderer = $this->getRenderer();
$view->setTemplate('tools/tools/alert');
$html = $renderer->render($view);
Why, you ask?
Because the Renderer is configured via the Zend Configuration. You can find that in the \Zend\Mvc\Service\ServiceManageFactory class. The alias configuration provided is the following:
'ViewPhpRenderer' => 'Zend\View\Renderer\PhpRenderer',
'ViewRenderer' => 'Zend\View\Renderer\PhpRenderer',
'Zend\View\Renderer\RendererInterface' => 'Zend\View\Renderer\PhpRenderer',
The alias'es are mapped to Factory:
'Zend\View\Renderer\PhpRenderer' => ViewPhpRendererFactory::class,
That Factory is:
class ViewPhpRendererFactory implements FactoryInterface
{
/**
* #param ContainerInterface $container
* #param string $name
* #param null|array $options
* #return PhpRenderer
*/
public function __invoke(ContainerInterface $container, $name, array $options = null)
{
$renderer = new PhpRenderer();
$renderer->setHelperPluginManager($container->get('ViewHelperManager'));
$renderer->setResolver($container->get('ViewResolver'));
return $renderer;
}
}
As such, it has some presets included when you use it with $this->getRenderer, namely it has the HelperPluginManager and the Resolver set. So it knows where to get additional resources (if needed) and it knows how to resolve (ie render) a View.
In ZF3 you would normally attach your event listener for the MvcEvent's in your module's Module.php like so:
<?php
namespace MyModule;
class Module
{
public function onBootstrap(MvcEvent $event)
{
$eventManager = $event->getApplication()->getEventManager();
$eventManager->attach(MvcEvent::EVENT_DISPATCH, function(MvcEvent $event) {
// Do someting...
});
}
}
Now there are two typical situations where your Module.php can grow big:
Your module has to handle multiple (or even all) MvcEvent's and maybe even treat them in different ways.
Your module has to perform multiple actions on a single MvcEvent.
What I'd like to be able to do is to specify a class name in my module.config.php along with one or multiple MvcEvent names to keep my Module.php nice and clean.
Is there a way to do this in Zend Framework 3?
#Nukeface has a great example but it does not directly answer my specific question.
To answer my own question:
This is possible with the use of listeners. A listener can be configured in the configuration files but it cannot be mapped to an event directly from the configuration alone.
It is possible to check for a specific setting in the configuration and determine what classes to map to what events. Even MvcEvents can be mapped this way.
Here's how to set it up:
1. The listener
We want to listen to multiple MvcEvents with one simple class. Note the class it extends.
namespace Demo\Listener;
class MyListener extends EventClassMapListener
{
public function handleEvent(MvcEvent $event)
{
// Do something
\Zend\Debug\Debug::dump($event->getName());
}
}
2. The abstract listener class
The above class needs a bit more body but that can be provided by the abstract listener class:
namespace Demo\Listener;
abstract class EventClassMapListener implements ListenerAggregateInterface
{
private $configuration;
public function __construct(array $configuration)
{
$this->configuration = $configuration;
}
public function attach(EventManagerInterface $events, $priority = 1)
{
$sharedManager = $events->getSharedManager();
foreach ($this->configuration as $identifier => $settings) {
foreach ($settings as $event => $configPriority) {
$sharedManager->attach($identifier, $event, [$this, 'handleEvent'], $configPriority ?: $priority);
}
}
}
public function detach(EventManagerInterface $events)
{
// Do the opposite of attach
}
abstract public function handleEvent(MvcEvent $event);
}
3. The factory
Now we need a factory that we can reuse for all our classes that need to listen to multiple events:
namespace Demo\Factory\Listener;
class EventClassmapListenerFactory implements FactoryInterface
{
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
$globalConfiguration = $container->get('config');
$configuration = [];
if (array_key_exists('event_classmap', $globalConfiguration)
&& array_key_exists($requestedName, $globalConfiguration['event_classmap'])
) {
$configuration = $globalConfiguration['event_classmap'][$requestedName];
}
return new $requestedName($configuration);
}
}
4. Configuration
In your module.config.php:
'service_manager' => [
'factories' => [
Listener\MyListener::class => Factory\Listener\EventClassmapListenerFactory::class,
],
],
'listeners' => [
Listener\MyListener::class,
],
'event_classmap' => [
// Name of the class that needs to listen to events
Listener\MyListener::class => [
// Identifier
\Zend\Mvc\Application::class => [
// List of event names and priorities
MvcEvent::EVENT_BOOTSTRAP => 1,
],
// Another identifier
MyEventEmitterClass::class => [
MyEventEmitterClass::EVENT_ONE,
MyEventEmitterClass::EVENT_TWO,
MyEventEmitterClass::EVENT_THREE,
],
],
],
Conclusion:
Although it might not really refined, I really like this idea. It is now fairly easy to add another listener and make it listen to a list of events from one or more emitters.
My opinion after some research
A listener itself should state what it wants to listen to, to keep things strict. Putting that information in a configuration file might result in a more complicated situation when it is not needed.
You need a few things for Listener classes:
Events
Listeners
Handlers
Factories
Config
Now, 2 & 3 are usually in the same class as you would usually have a Listener class for a specific purpose. Such as "Listen for Rocket launch and steer Rocket to Mars".
As such, you would need to "create" these "events" to listen for somewhere. Such as a DemoEvents class!
namespace Demo\Event;
use Zend\EventManager\Event;
class DemoEvent extends Event
{
const THE_STRING_TO_LISTEN_FOR = 'rocket.ready.for.launch';
const ANOTHER_STRING_TO_LISTEN_FOR = 'rocket.steer.to.mars';
}
Now that we have "events", we need to "listen" for them. For that we need a Listener. Because I'm limiting this to 1 example, the Handler (function(-ality) to be executed when the "event" we're "listening" for is "heard") will be in the same class.
namespace Demo\Listener;
use Demo\Event\DemoEvent;
use Zend\EventManager\Event;
use Zend\EventManager\EventManagerInterface;
use Zend\EventManager\ListenerAggregateInterface;
class DemoListener implements ListenerAggregateInterface
{
/**
* #var array
*/
protected $listeners = [];
/**
* #param EventManagerInterface $events
*/
public function detach(EventManagerInterface $events)
{
foreach ($this->listeners as $index => $listener) {
if ($events->detach($listener)) {
unset($this->listeners[$index]);
}
}
}
/**
* #param EventManagerInterface $events
*/
public function attach(EventManagerInterface $events, $priority = 1)
{
$sharedManager = $events->getSharedManager();
$sharedManager->attach(Demo::class, DemoEvent::THE_STRING_TO_LISTEN_FOR, [$this, 'doSomethingOnTrigger'], -10000);
}
/**
* Apart from triggering specific Listener function and de-registering itself, it does nothing else. Add your own functionality
*
* #param Event $event
*/
public function doSomethingOnTrigger(Event $event)
{
// Gets passed along parameters from the ->trigger() function elsewhere
$params = $event->getParams();
$specificClass = $params[SpecificClass::class];
// Do something useful here
$specificClass->launchRocketIntoOrbit();
// Detach self to prevent running again
$specificClass->getEventManager()->getSharedManager()->clearListeners(get_class($specificClass), $event->getName());
// NOTE: USE THIS TRIGGER METHODOLOGY ELSEWHERE USING THE STRING FROM THE ATTACH() FUNCTION TO TRIGGER THIS FUNCTION
// Trigger events specific for the Entity/class (this "daisy-chains" events, allowing for follow-up functionality)
$specificClass->getEventManager()->trigger(
DemoEvent::ANOTHER_STRING_TO_LISTEN_FOR,
$specificClass ,
[get_class($specificClass) => $specificClass ] // Params getting passed along
);
}
}
Excellent. We now have a events, a listener and a handler. We just need a factory to create this class when needed.
namespace Demo\Factory;
use Demo\Listener;
use Interop\Container\ContainerInterface;
use Zend\ServiceManager\Factory\FactoryInterface;
class DemoListenerFactory implements FactoryInterface
{
/**
* #param ContainerInterface $container
* #param string $requestedName
* #param array|null $options
* #return object|DemoListener
* #throws \Psr\Container\ContainerExceptionInterface
* #throws \Psr\Container\NotFoundExceptionInterface
*/
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
// If you're implementation of the Listener has any requirements, load them here and add a constructor in the DemoListener class
return new DemoListener();
}
}
Lastly, we need some config. Obviously we need to register the Listener + Factory combination. Let's do that first.
namespace Demo;
use Demo\Listener\DemoListener;
use Demo\Listener\DemoListenerFactory;
'service_manager' => [
'factories' => [
DemoListener::class => DemoListenerFactory::class,
],
],
Now for a little known bit of config to make sure that the Listener gets registered as a Listener:
'listeners' => [
DemoListener::class
],
Yep, that's it.
Make sure to add both of these bits of config at the first level of config, they're siblings.
I'm having some trouble getting my unit test to work. I'm testing a controller that uses a service that is created by a factory. What I want to achieve is to replace a factory with a mocked service so I can perform tests without using an active database connection.
The setup
In my service manager's configuration file I point to a factory.
The factory requires an active database connection that I don't want to use during my unit test.
Namespace MyModule;
return [
'factories' => [
MyService::class => Factory\Service\MyServiceFactory::class,
],
];
Note: I have changed class names and simplified configuration for illustration purposes.
The service uses a mapper that I won't be going into now because that is not relevant to the situation. The mappers are tested in their own testcases. The service itself has it's own testcase as well but needs to be present for the controller's actions to work.
The controller action simply receives information from the service.
Namespace MyModule\Controller;
use MyModule\Service\MyService;
use Zend\Mvc\Controller\AbstractActionController;
class MyController extends AbstractActionController
{
/**
* #var MyService
*/
private $service;
/**
* #param MyService $service
*/
public function __construct(MyService $service)
{
$this->service = $service;
}
/**
* Provides information to the user
*/
public function infoAction()
{
return [
'result' => $this->service->findAll(),
];
}
}
Note: Again, I have changed class names and simplified the example for illustration purposes.
What I've tried
In my testcase I've tried to override the desired factory like this:
/**
* #return \Prophecy\Prophecy\ObjectProphecy|MyService
*/
private function mockService()
{
$service = $this->prophesize(MyService::class);
$service->findAll()->willReturn(['foo', 'bar']);
return $service;
}
/**
* #param \Zend\ServiceManager\ServiceManager $services
*/
private function configureServiceManager(ServiceManager $services)
{
$services->setAllowOverride(true);
$services->setService(MyService::class, $this->mockService()->reveal());
$services->setAllowOverride(false);
}
At first sight this looks great, but it doesn't work. It just seems to append the service to the service manager's list of services, not overriding the factory.
Changing $services->setService to $services->setFactory requires me to build another factory. What I could do is create a factory that injects a mock-mapper into the service but that feels wrong. I'm testing the controller, not the service or mapper so I am trying to avoid complex solutions like that to keep my test cases simple and clear.
Are there any options regarding my situation? Is it possible to override a factory with a service in the service manager or am I looking at it wrong?
I think you need a separate config file for unit testing.
phpunit.xml
<?xml version="1.0"?>
<phpunit bootstrap="./Bootstrap.php">
Bootstrap.php
require 'vendor/autoload.php';
$configuration = include 'config/phpunit.config.php';
Zend\Mvc\Application::init ($configuration);
config/phpunit.config.php is a config file created for unit testing only:
config/phpunit.config.php
$configuration = include (__DIR__ . '/application.config.php');
$configuration ['module_listener_options'] ['config_glob_paths'] [] = 'config/phpunit/{,*.}local.php';
config/phpunit/yourfile.local.php
return [
'service_manager' => array (
'factories' => [
MyService::class => ...
]
)
];
In config/phpunit/yourfile.local.php you can let MyService::class be whatever you want, even a closure.
There is no need to build new factories for this. Just use a simple closure instead:
/**
* #param \Zend\ServiceManager\ServiceManager $services
*/
private function configureServiceManager(ServiceManager $services)
{
$services->setAllowOverride(true);
$mockedService = $this->mockService();
$services->setFactory(MyService::class, function() use ($mockedService) {
$mockedService->reveal();
});
$services->setAllowOverride(false);
}
Now you can still mock only the required service. Adding expectations in the test case is still as flexible as it should be:
public function testMyCase()
{
$expected = ['foo', 'bar'];
$this->mockService()->findAll()->willReturn($expected);
$result = $this->service->findAll();
$this->assertSame($expected, $result);
}
I'm not sure how to formulate the question, so feel free to edit it.
My current situation is as following:
I have a factory class which instantiates a form class. Dependency Injection (DI) is done via constructor injection. My problem is, that this form element has a Doctrine ObjectMultiCheckbox which requires a findby-method. For this findby-method I need the ID of a certain entity, but I cannot pass the ID through the factory class to the form.
My Question is, how can I deal with this situation? What is the best approach?
Let's say this is my factory class:
class CustomerFormFactory implements FactoryInterface
{
/**
* Create service
*
* #param ServiceLocatorInterface $serviceLocator
* #return Form
*/
public function createService(ServiceLocatorInterface $serviceLocator)
{
$em = $serviceLocator->get('Doctrine\ORM\EntityManager');
return new CustomerForm($em);
}
}
And I get the form via the service locator like this:
$customerForm = $this->getServiceLocator()->get('CustomerForm');
How can I pass the ID to the service locator? And if the form element requires a certain ID, doesn't it break the purpose of DI and services? Should I go for the "classic" way and instantiate the form element by myself like this:
$customerForm = new CustomerForm(EntityManager $em, int $id);
I'm really not sure what I should do or what is the best way to handle this.
In order to insert options into your form you could use the CreationOptions of the factory class.
So lets start by setting up our configurations for the FormElementManager (a serviceLocator for our Form Elements).
Within your Module.php:
use Zend\ModuleManager\Feature\FormElementProviderInterface;
class Module implements FormElementProviderInterface
{
// your module code
public function getFormElementConfig()
{
return [
'factories' => [
'myForm' => \Module\Form\MyFormFactory::class
]
];
}
}
After we've set up the configruation we should create our Factory, which returns the Form including it's dependencies. We also insert the options which we can re-use within our form class.
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\MutableCreationOptionsTrait;
use Zend\ServiceManager\ServiceLocatorInterface;
class MyFormFactory implements FactoryInterface
{
use MutableCreationOptionsTrait;
/**
* Create service
*
* #param ServiceLocatorInterface $serviceLocator
*
* #return mixed
*/
public function createService(ServiceLocatorInterface $serviceLocator)
{
return new MyForm(
$serviceLocator->getServiceLocator()->get('Doctrine\ORM\EntityManager'),
'MyForm',
$this->getCreationOptions()
);
}
}
When using ZF3 it is better to use \Zend\ServiceManager\Factory\FactoryInterface instead of the \Zend\ServiceManager\FactoryInterface as this is the way ZF3 is going with using factories. In the example above I used the ZF2 (v2.7.6 zendframework/zend-servicemanager) version. See the comment on the class Zend\ServiceManager\FactoryInterface::class to replace it with the ZF3 version.
So now when we call ::get('myForm', ['id' => $id]) on the FormElementManager class you will get a MyForm instance and the options of the form will contain the options we've passed along.
So your form might look something similar:
class MyForm extends \Zend\Form\Form
{
public function __construct(
\Doctrine\Common\Persistence\ObjectManager $entityManager,
$name = 'myForm',
$options = []
) {
parent::__construct($name, $options);
$this->setEntityManager($entityManager);
}
public function init () {
/** add form elements **/
$id = $this->getOption('id');
}
}
You can also create the form and set the entityManager, but that is all up to you. You don't need to use constructor injection.
So an exmaple for your controller:
$myForm = $this->getServiceManager()->get('FormElementManager')->get('myForm', ['id' => 1337]);
$options = $myForm->getOptions();
// your options: ['id' => 1337]
You might not have the ServiceManager or Locator within your Controller as you're using ZF2.5+ or ZF3 so you've got to inject the FormElementManager or the Form class into your Controller by factory.
In case you don't have any other dependencies within your form but you want to set the options, you don't need to create a factory for each class. You can re-use the InvokableFactory::class as this will also inject the creationOptions.
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()