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();
Related
I'm trying to print all routes from my modules on "some page" with var_dump() or whatever debug function.
I have found lots of posts and samples but I can't get them printed and most examples fail in my code.
So far I think this is the best way to do so but where to use this code ?
// $sl instanceof Zend\ServiceManager\ServiceManager
$config = $sl->get('Config');
$routes = $config['router']['routes'];
If you want to view all routes just for debugging purposes, you can use var_dump or similar on the router object:
// $sl instanceof Zend\ServiceManager\ServiceManager
$router = $sl->get('Router');
var_dump($router);
You may print all routes from in your controller's method. Look at the following example
module/Application/src/Application/Controller/IndexController.php
<?php
namespace Application\Controller;
use Zend\View\Model\ViewModel;
use Zend\Mvc\Controller\AbstractActionController;
class IndexController extends AbstractActionController
{
/**
* #var array
*/
protected $routes;
/**
* #param array $routes
*/
public function __construct(array $routes)
{
// Here is the catch
$this->routes = $routes;
}
public function indexAction()
{
// Thus you may print all routes
$routes = $this->routes;
echo '<pre>';
print_r($routes);
echo '</pre>';
exit;
return new ViewModel();
}
}
As we passed an array of routes to the constructor of IndexController. We need to make an factory of this controller. A factory is a class that creates instances of other classes.
module/Application/src/Application/Controller/IndexControllerFactory.php
<?php
namespace Application\Controller;
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
class IndexControllerFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $serviceLocator)
{
$serviceManager = $serviceLocator->getServiceLocator();
$config = $serviceManager->get('Config');
$routes = $config['router'];
return new IndexController($routes);
}
}
A invokable class can not be constructed with arguments. Our controller would not work as invokables because we know we already passed an argument to its constructor. So we need to configure that in factories key under controllers key of our module.config.php
module/Application/config/module.config.php
'controllers' => [
'invokables' => [
// This would not work any more as we created a factory of it
// 'Application\Controller\Index' => 'Application\Controller\IndexController',
],
// We should do it thus
'factories' => [
'Application\Controller\Index' => 'Application\Controller\IndexControllerFactory',
],
],
This answer has been edited for good practice as #av3 suggested!
I am quite new to ZF2 and I am preparing a demo application with simple login and CRUD system. Now for login I have prepared a plugin which consists of some functions that will authenticate users, return the logged in user data, return the logged in status etc. But the problem that I am facing is I can't initialize any variable into the constructor of my controller which will store any return value from the plugin. It's always showing service not found exception.
Please find my plugin code below:
AuthenticationPlugin.php
<?php
namespace Album\Controller\Plugin;
use Zend\Mvc\Controller\Plugin\AbstractPlugin;
use Zend\Session\Container as SessionContainer;
use Zend\View\Model\ViewModel;
use Album\Entity\User;
class AuthenticationPlugin extends AbstractPlugin{
protected $entityManager;
protected $usersession;
public function __construct(){
$this->usersession = new SessionContainer('UserSession');
}
public function dologin($email,$password)
{
$getData = $this->em()->getRepository('Album\Entity\User')->findOneBy(array('email' => $email, 'password' => $password));
if(count($getData)){
$this->usersession->offsetSet('userid', $getData->getId());
return true;
}
else{
return false;
}
}
public function isloggedin(){
$userid = $this->usersession->offsetGet('userid');
if(!empty($userid)){
return true;
}
else{
return false;
}
}
public function logindata(){
$userid = $this->usersession->offsetGet('userid');
$getData = $this->em()->getRepository('Album\Entity\User')->findOneBy(array('id' => $userid));
return $getData;
}
public function logout(){
$this->usersession->offsetUnset('userid');
}
public function em(){
return $this->entityManager = $this->getController()->getServiceLocator()->get('Doctrine\ORM\EntityManager');
}
}
?>
In my module.config.php
'controller_plugins' => array(
'invokables' => array(
'AuthPlugin' => 'Album\Controller\Plugin\AuthenticationPlugin',
)
),
Now I am doing this in my controller:
protected $entityManager;
protected $isloggedin;
protected $authentication;
public function __construct(){
$this->authentication = $this->AuthPlugin();
$this->isloggedin = $this->authentication->isloggedin();
}
The error I am getting is like below:
An error occurred An error occurred during execution; please try again
later. Additional information:
Zend\ServiceManager\Exception\ServiceNotFoundException
File:
D:\xampp\htdocs\subhasis\zf2-tutorial\vendor\zendframework\zendframework\library\Zend\ServiceManager\ServiceManager.php:555
Message:
Zend\Mvc\Controller\PluginManager::get was unable to fetch or create an instance for AuthPlugin
But if I write the above constructor code in any of my controller actions everything is fine. in ZF1 I could initialize any variable in the init() method and could use the variable in any of my actions. How can I do this in ZF2? Here, I want to detect if the user is logged in the constructor itself. Now I have to call the plugin in every action which I don't want.
What should I do here?
The error you are receiving is because you are trying to use the ServiceManager (via the Zend\Mvc\Controller\PluginManager) in the __construct method of the controller.
When a controller is registered as an invokable class, the Service Manager (ControllerManager) is responsible for the creating the controller instance. Once created, it will then call the controllers various default 'initializers' which also inlcudes the plugin manager. By having your code in __construct it is trying to use the plugin manager before it has been set.
You can resolve this by using a controller factory, rather than an invokable in module.config.php.
'controllers' => [
'factories' => [
'MyModule\Controller\Foo' => 'MyModule\Controller\FooControllerFactory',
],
],
Then the factory
namespace MyModule\Controller\FooControllerFactory;
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
class FooControllerFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $controllerManager)
{
$serviceManager = $controllerManager->getServiceLocator();
$controllerPluginManager = $serviceManager->get('ControllerPluginManager');
$authPlugin = $controllerPluginManager->get('AuthPlugin');
return new FooController($authPlugin);
}
}
Lastly, update the controller __construct to add the new argument and remove the call to $this->authPlugin()
class FooController extends AbstractActionController
{
public function __construct(AuthPlugin $authentication)
{
$this->authentication = $authentication;
$this->isloggedin = $authentication->isloggedin();
}
}
I found a few other posts relevant to this issue, however i wasn't able to achieve what i wanted so i decided to delete everything and start over with some help...
This is my work so far, which does the job but the data are provided hard coded in an array and i need to create a database connection to fetch those data.
In my module class i have:
public function getViewHelperConfig()
{
return array(
'factories' => array(
'liveStreaming' => function() {
return new LiveStreaming();
},
),
);
}
This is the code i have in my view helper:
namespace Application\View\Helper;
use Zend\View\Helper\AbstractHelper;
class LiveStreaming extends AbstractHelper
{
protected $liveStreamingTable;
public function __invoke()
{
$events = array(
'1' => array('name' => 'Event name',
'sport' => 'Soccer',
'time' => '11:30'),
'2' => array('name' => 'Event name',
'sport' => 'Soccer',
'time' => '17:00'),
);
return $events;
//this is what should be used (or something like that) to get the data from the db...
//return array('events' => $this->getLiveStreamingTable()->fetchAll() );
}
public function getLiveStreamingTable()
{
if (!$this->liveStreamingTable) {
$sm = $this->getServiceLocator();
$this->liveStreamingTable = $sm->get('LiveStreaming\Model\LiveStreamingTable');
}
return $this->liveStreamingTable;
}
}
So, i want to get the $events array from the database. I've created Application\Model\LiveStreaming and Application\Model\LiveStreamingTable (following the instructions of the ZF2 official tutorial) and i need some help proceeding to the next step, which should probably have to do with the service locator.
You seem to be almost there. The only thing missing is the ability to call $this->getServiceLocator(); from within the view helper (as the AbstractHelper doesn't provide this method).
There are two options
Inject the LiveStreamingTable into the view helper directly
inject the ServiceManager itself and create the LiveStreamingTable within the helper
Option 1 Make LiveStreamingTable a dependency of the view helper (type hint in constructor)
namespace Application\View\Helper;
use Zend\View\Helper\AbstractHelper;
use LiveStreaming\Model\LiveStreamingTable;
class LiveStreaming extends AbstractHelper
{
protected $liveStreamingTable;
public function __construct(LiveStreamingTable $liveStreamingTable)
{
$this->liveStreamingTable = $liveStreamingTable;
}
public function getLiveStreamingTable()
{
return $this->liveStreamingTable;
}
}
And the factory becomes:
public function getViewHelperConfig()
{
return array(
'factories' => array(
'liveStreaming' => function($sl) {
// Get the shared service manager instance
$sm = $sl->getServiceLocator();
$liveStreamingTable = $sm->get('LiveStreaming\Model\LiveStreamingTable');
// Now inject it into the view helper constructor
return new LiveStreaming($liveStreamingTable);
},
),
);
}
Option 2 - Implement the ServiceLocatorAwareInterface (making it again a dependency of the view helper)
namespace Application\View\Helper;
use Zend\View\Helper\AbstractHelper;
use Zend\ServiceManager\ServiceLocatorAwareInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
class LiveStreaming extends AbstractHelper implements ServiceLocatorAwareInterface
{
protected $serviceLocator;
protected $liveStreamingTable;
public function __construct(ServiceLocatorInterface $serviceLocator)
{
$this->serviceLocator = $serviceLocator;
}
public function setServiceLocator(ServiceLocatorInterface $serviceLocator);
public function getServiceLocator();
public function getLiveStreamingTable()
{
if (null == $this->liveStreamingTable) {
$this->liveStreamingTable = $this->getServiceLocator()->get('LiveStreaming\Model\LiveStreamingTable');
}
return $this->liveStreamingTable;
}
}
Your factory will then look like:
public function getViewHelperConfig()
{
return array(
'factories' => array(
'liveStreaming' => function($sl) {
// Get the shared service manager instance
$sm = $sl->getServiceLocator();
// Now inject it into the view helper constructor
return new LiveStreaming($sm);
},
),
);
}
Personally, I feel that Option 1 makes more sense from a Dependency Injection (DI) point of view - It's clear that the LiveStreamingTable is what is needed to create the view helper.
Edit
Make sure you have the LiveStreaming\Model\LiveStreamingTable service also registered with the service manager (as we request it in the above code when we did $sm->get('LiveStreaming\Model\LiveStreamingTable');)
// Module.php
public function getServiceConfig()
{
return array(
'factories' => array(
'LiveStreaming\Model\LiveStreamingTable' => function($sm) {
// If you have any dependencies for the this instance
// Such as the database adapter etc either create them here
// or request it from the service manager
// for example:
$foo = $sm->get('Some/Other/Registered/Service');
$bar = new /Directly/Created/Instance/Bar();
return new \LiveStreaming\Model\LiveStreamingTable($foo, $bar);
},
),
);
}
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();
I have custom router and I have to get access to Zend\Navigation inside this custom router. I was googling, asking and searching and no results :/
All I need is to find nodes with 'link' param using Zend\Navigation in my Alias::match function.
Here is my module.config.php:
'navigation' => array(
'default' => array(
'account' => array(
'label' => 'Account',
'route' => 'node',
'pages' => array(
'home' => array(
'label' => 'Dashboard',
'route' => 'node',
'params' => array(
'id' => '1',
'link' => '/about/gallery'
),
),
),
),
),
),
[...]
And here is my Alias class:
// file within ModuleName/src/ModuleName/Router/Alias.php
namespace Application\Router;
use Traversable;
use Zend\Mvc\Router\Exception;
use Zend\Stdlib\ArrayUtils;
use Zend\Stdlib\RequestInterface as Request;
use Zend\Mvc\Router\Http;
use Zend\ServiceManager\ServiceLocatorAwareInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
class Alias extends Http\Segment implements ServiceLocatorAwareInterface
{
public function setServiceLocator(ServiceLocatorInterface $serviceLocator)
{
$this->serviceLocator = $serviceLocator;
return $this;
}
public function getServiceLocator()
{
return $this->serviceLocator;
}
public function match(Request $request, $pathOffset = null)
{
[...]
return parent::match($request, $pathOffset);
}
}
EDITED:
Now i know that I should inject service manager into my custom router. Let me know if You know how to do this :)
EDITED:
Ok, its not custom router but route. My bad. I was talking on #zftalk irc chanell and AliasSegment class should implements ServiceLocatorAwareInterface. Ok I've tried it but now there is another problem.
In setServiceLocator function i can't get service locator. It returns null object, however $serviceLocator is class Zend\Mvc\Router\RoutePluginManager.
public function setServiceLocator(ServiceLocatorInterface $serviceLocator){
$sl = $serviceLocator->getServiceLocator();
var_dump($sl); // NULL
}
Any ideas how to get Zend navigation from it ?
EDITED
Corresponding to what #mmmshuddup said, I've changed my custom router class. (New version is above). Also in my Module.php, within onBootstrap function, I added this line:
$sm->setFactory('Navigation', 'Zend\Navigation\Service\DefaultNavigationFactory', true);
Navigation works and its instantiated before route so it should be visible within my Alias class but it's not.
I've put into my match function in Alias class this line:
$servicesArray = $this->getServiceLocator()->getRegisteredServices();
and $servicesArray is almost empty. There is no service, no factories. The same line inserted into onBootstrap, just after setting new factory (as above) returns array with navigation and other services.
The question is: how can i share this array (or ServiceManager) with my custom router: Alias ?
I have to say that all I want to do was possible in ZF1 and it was quite easy.
EDIT
I found a solution. The answer is below
That is because the object itself really doesn't have any properties declared. But if you do this:
echo get_class($sl);
You will see that it is indeed an instance of Zend\ServiceManager\ServiceManager
You should be able to get your navigation instance by doing something like:
$nav = $sl->get('Navigation');
EDIT:
I just notice you have some stuff in the wrong location of your code. You're calling getServiceLocator() on $serviceLocator which is already the instance of that. Also you're calling it within setServiceLocator(). You should change it to:
// EDIT - file within ModuleName/src/Router/Alias.php
namespace Application\Router;
use Traversable;
use Zend\Mvc\Router\Exception;
use Zend\Stdlib\ArrayUtils;
use Zend\Stdlib\RequestInterface as Request;
use Zend\Mvc\Router\Http;
class Alias extends Http\Segment implements ServiceLocatorAwareInterface
{
public function setServiceLocator(ServiceLocatorInterface $serviceLocator)
{
$this->serviceLocator = $serviceLocator;
return $this;
}
public function getServiceLocator()
{
return $this->serviceLocator;
}
public function match(Request $request, $pathOffset = null)
{
$nav = $this->getServiceLocator()->get('Navigation');
// ...
return parent::match($request, $pathOffset);
}
}
I found the solution but this is NOT elegant solution i think. However everything works perfectly. If somebody knows disadvantages of this solution, please comment this answer or add another, better. I had to modify #mmmshuddup's idea (you can read the conversation).
First of all, the implementation of ServiceLocatorAwareInterface in custom route class is no more necessary.
In Module.php within onBootstrap function:
$app = $e->getApplication();
$sm = $app->getServiceManager();
$sm->get('translator');
$eventManager = $e->getApplication()->getEventManager();
$moduleRouteListener = new ModuleRouteListener();
$moduleRouteListener->attach($eventManager);
$sm->setFactory('Navigation',
'Zend\Navigation\Service\DefaultNavigationFactory', true);
$nav = $sm->get('Navigation');
$alias = $sm->get('Application\Router\Alias');
$alias->setNavigation($nav);
First we instantiate Navigation factory in ServiceManager and then our custom route. After that we can pass Navigation class into custom route using setNavigation function.
To complete instantiate of our custom route we need in getServiceConfig in the same file:
return array(
'factories' => array(
'Application\Router\Alias' => function($sm) {
$alias = new \Application\Router\Alias('/node[/:id]');
return $alias;
},
'db_adapter' => function($sm) {
$config = $sm->get('Configuration');
$dbAdapter = new \Zend\Db\Adapter\Adapter($config['db']);
return $dbAdapter;
},
)
);
And here is a tricky part. This instance is temporary. While routing, this class will be instantiated one more time and this is why, I think, it's not very elegant. We have to insert parameter into constructor however at this moment value of this parameter is not important.
The custom route class:
// file within ModuleName/src/ModuleName/Router/Alias.php
namespace Application\Router;
use Traversable;
use Zend\Mvc\Router\Exception;
use Zend\Stdlib\ArrayUtils;
use Zend\Stdlib\RequestInterface as Request;
use Zend\Mvc\Router\Http;
class Alias extends Http\Segment
{
private static $_navigation = null;
public function match(Request $request, $pathOffset = null)
{
//some logic here
//get Navigation
$nav = self::$_navigation;
return parent::match($request, $pathOffset);
}
public function setNavigation($navigation){
self::$_navigation = $navigation;
}
}
Because first instance is temporary, we have to collect our Navigation class in static variable. It's awful but works nice. Maybe there is a way to instantiate it only once and in route configuration get instance of it, but at this moment this is best answer for my question. Simply enough and working correctly.