I am using a Restful Controller and on certain conditions, I would like the trigger the MvcEvent::EVENT_DISPATCH_ERROR and stop execution of the controller immediately after. In my Module class, I have attached an event listener for this but I can't find a way to trigger it from the view controller.
My Module code is:
public function onBootstrap(MvcEvent $mvcEvent) {
$eventManager = $mvcEvent->getApplication()
->getEventManager();
$eventManager->attach(array(MvcEvent::EVENT_DISPATCH_ERROR, MvcEvent::EVENT_RENDER_ERROR), array($this, 'error'));
}
public function error(MvcEvent $mvcEvent) {
echo $mvcEvent->getError();
die();
}
and my Controller code is:
public function indexAction() {
$mvcEvent = $this->getEvent();
$mvcEvent->setError('test-error-code');
$mvcEvent->getTarget()->getEventManager()->trigger(MvcEvent::EVENT_DISPATCH_ERROR, $mvcEvent);
return;
}
I think the problem is that you're not attaching to the Application's sharedEventManager. You can also use the Controller's own Event Manager to trigger the event.
Try something like this:
Module.php
public function onBootstrap(MvcEvent $mvcEvent) {
$eventManager = $mvcEvent->getApplication()->getEventManager()->getSharedManager();
$eventManager->attach('Zend\Stdlib\DispatchableInterface', MvcEvent::EVENT_DISPATCH_ERROR, array($this, 'error'));
}
Controller
public function indexAction() {
$mvcEvent = $this->getEvent();
$mvcEvent->setError('test-error-code');
$this->getEventManager()->trigger(MvcEvent::EVENT_DISPATCH_ERROR, $mvcEvent);
return;
}
Related
I am trying to add event for Laminas Framework that will fire when \Laminas\Mvc\MvcEvent::EVENT_DISPATCH is triggered. But absolutelly nothing happends, like this triggers not exists. What am I doing wrong?
This is the code under the module\Application\src\Module.php:
use Laminas\ModuleManager\ModuleManager;
use Laminas\Mvc\MvcEvent;
class Module
{
public function init(ModuleManager $moduleManager)
{
ini_set("display_errors", '1');
$eventManager = $moduleManager->getEventManager();
$eventManager->attach(MvcEvent::EVENT_DISPATCH, [$this, 'onDispatch']);
}
public function onDispatch(\Laminas\EventManager\Event $event)
{
var_dump('ok');die;
}
}
I think you need use another method in Module it's should be something like this:
use Laminas\Mvc\MvcEvent;
class Module
{
public function onBootstrap(MvcEvent $event)
{
$application = $event->getApplication();
$eventManager = $application->getEventManager();
$eventManager->attach(MvcEvent::EVENT_DISPATCH, [$this, 'onDispatch']);
}
public function onDispatch(MvcEvent $event)
{
var_dump('ok');
die;
}
}
In this case it onBootstrap. Hope help you
On init you'll need to get the shared event manager from the module manager:
<?php
use Laminas\ModuleManager\Feature\InitProviderInterface;
use Laminas\ModuleManager\ModuleManagerInterface;
use Laminas\Mvc\Application;
use Laminas\Mvc\MvcEvent;
final class Module implements InitProviderInterface
{
public function init(ModuleManagerInterface $manager): void
{
$sharedEventManager = $manager->getEventManager()->getSharedManager();
$sharedEventManager->attach(
Application::class,
MvcEvent::EVENT_DISPATCH,
function () {
var_Dump('dispatch from init');
}
);
}
}
The SharedEventManager is usually (or should be) shared between all event manager instances. This makes it possible to call or create events from other event manager instances. To differentiate between event names an identifier is used (so you can have more then one event with the same name). All MvcEvents belong to the Laminas\Mvc\Application identifier. Laminas\ModuleManager\ModuleManager has it's own EventManager instance, that is why you'll need to add the event to the SharedEventManager (init() is called by the ModuleManager and Laminas\ModuleManager\ModuleEvent is used).
onBootstrap() will be called by Laminas\Mvc\Application, that why you get the correct EventManager instance there.
As #Dimitry suggested: you should add that event in onBootstrap() as the dispatching process is part of the application and not the module manager. In init() you should only add bootstrap events.
And btw: you should use the Laminas\ModuleManager\Feature\* interfaces to make your application a bit more robust to future updates.
How can I listen to the dispatch event of a specific controller? At the moment I do the following:
Module.php
public function onBootstrap(EventInterface $event) {
$application = $event->getApplication();
$eventManager = $application->getEventManager();
$serviceManager = $application->getServiceManager();
$eventManager->attach($serviceManager->get('MyListener'));
}
MyListener.php
class MyListener extends AbstractListenerAggregate {
public function attach(EventManagerInterface $eventManager) {
$this->listeners[] = $eventManager->attach(
MvcEvent::EVENT_DISPATCH, function($event) {
$this->setLayout($event);
}, 100
);
}
public function setLayout(EventInterface $event) {
$event->getViewModel()->setTemplate('mylayout');
}
}
This sets the layout for all controller dispatches. Now I want to set the layout only if the application dispatches a specific controller.
Like all Modules have an onBootstrap() method, all controllers extending AbstractController have an onDispatch() method.
Considering you want to apply a different layout for a single specific controller, you can simply do the following:
<?php
namespace MyModule\Controller;
use Zend\Mvc\Controller\AbstractActionController; // Or AbstractRestfulController or your own
use Zend\View\Model\ViewModel; // Or JsonModel or your own
use Zend\Mvc\MvcEvent;
class MyController extends AbstractActionController
{
public function onDispatch(MvcEvent $e)
{
$this -> layout('my-layout'); // The layout name has been declared somewhere in your config
return parent::onDispatch($e); // Get back to the usual dispatch process
}
// ... Your actions
}
You may do this for every controller that has a special layout. For those who don't, well, you don't have to write anything.
If you often need to change your layout (e.g. you have to handle not a single controller but several), you can attach an MvcEvent in your module.php to get your layout setting code in one place.
To keep things simple, I'm not using a custom listener here, but you may use one as well.
<?php
namespace MyModule;
use Zend\Mvc\MvcEvent;
class Module
{
public function onBootstrap(MvcEvent $e)
{
$eventManager = $e -> getApplication() -> getEventManager();
$eventManager -> attach(
MvcEvent::EVENT_DISPATCH,
// Add dispatch error event only if you want to change your layout in your error views. A few lines more are required in that case.
// MvcEvent::EVENT_DISPATCH | MvcEvent::EVENT_DISPATCH_ERROR
array($this, 'onDispatch'), // Callback defined as onDispatch() method on $this object
100 // Note that you don't have to set this parameter if you're managing layouts only
);
}
public function onDispatch(MvcEvent $e)
{
$routeMatch = $e -> getRouteMatch();
$routeParams = $routeMatch -> getParams();
switch ($routeParams['__CONTROLLER__']) {
// You may use $routeParams['controller'] if you need to check the Fully Qualified Class Name of your controller
case 'MyController':
$e -> getViewModel() -> setTemplate('my-first-layout');
break;
case 'OtherController':
$e -> getViewModel() -> setTemplate('my-other-layout');
break;
default:
// Ignore
break;
}
}
// Your other module methods...
}
You have to attach your event listener to the SharedEventManager and listen MvcEvent::EVENT_DISPATCH of the "Zend\Stdlib\DispatchableInterface" interface.
See an example:
$eventManager->getSharedManager()
->attach(
'Zend\Stdlib\DispatchableInterface',
MvcEvent::EVENT_DISPATCH,
$serviceManager->get('MyListener')
);
Within your listener you can get the instance of the target controller like so $controller = $event->getTarget();
So, eventually, the method "setLayout" may look like this:
public function setLayout(MvcEvent $event)
{
$controller = $event->getTarget();
if ($controller instanceof MyController)
{
$event->getViewModel()->setTemplate('mycontroller-layout');
}
}
How can I pass data to controllers from Module class?
I need to pass data from onBootstrap method to all module controllers. What is the best way to do this. I can access controller using $e->getTarget() but don't know how to pass custom data to it. Maybe controller has storage for that?
The controller has access to the MvcEvent you can setup an event listener to attach arbitrary data to it and then fetch it within the controller.
Module.php
public function onBootstrap(MvcEvent $event)
{
$event->setParam('foo', 'bar');
}
Controller
public function fooAction() {
$foo = $this->getEvent()->getParam('foo', false);
}
#JonDay suggested an event listener which would also work well.
public function onBootstrap(MvcEvent $event)
{
$application = $event->getApplication();
$eventManager = $application->getEventManager()->getSharedManager();
$eventManager->attach('Zend\Mvc\Controller\AbstractActionController', 'dispatch', function($event) {
$controller = $event->getTarget();
// Set public property
$controller->foo = 'bar';
// OR protected with setter
$controller->setFoo('bar');
});
}
I want to do some logic before MvcEvent::EVENT_DISPATCH, but MvcEvent::getTarget() function returns object of Mvc\Application instead of Controller if I set priority above 1 like:
$e->getApplication()->getEventManager()->attach(MvcEvent::EVENT_DISPATCH, array($this, 'routing'), 100);
If I set priority to negative value, I get the Controller object, but it's fired after action-function. How can I get the Controller object in this case?
you can either use MvcEvent::getRouteMatch() to determine what controller will be dispatched (if any), or attach to the controller event manager inside the controller itself by overriding AbstractActionController:setEventManager().
ie:
// in your controller
public function setEventManager(EventManagerInterface $events)
{
parent::setEventManager($events);
$events->attach(MvcEvent::EVENT_DISPATCH, array($this, 'preDispatch'), 100);
}
public function preDispatch(MvcEvent $event)
{
// your custom logic here
}
// in your Module
public function onBootstrap(MvcEvent $e)
{
$e->getApplication()
->getEventManager()
->getSharedManager()
->attach(
'Zend\Stdlib\DispatchableInterface',
MvcEvent::EVENT_DISPATCH, array($this, 'onDispatch'),
2
);
}
public function onDispatch(MvcEvent $a)
{
$target = $a->getTarget();
}
I try to use plugin to dispatch bootstraps for different modules. However, for some reason, I can not trigger controller for each module, and the error is "EXCEPTION_NO_CONTROLLER". Any one could give some advice about it?
// Plugin Code:
class Plugin_AccessCheck extends Zend_Controller_Plugin_Abstract {
public function __construct() {
}
public function dispatchLoopStartup(Zend_Controller_Request_Abstract $request) {
if ('admin' == $request->getModuleName()) {
require_once APPLICATION_PATH .'/modules/admin/Bootstrap.php';
$moduleBootstrap = new Admin_Bootstrap();
$moduleBootstrap->bootstrap();
} else if('site' == $request->getModuleName()) {
}
}
}
// Module Bootstrap:
class Admin_Bootstrap extends Zend_Application_Module_Bootstrap{
public function Admin_Bootstrap() {
}
protected function _initAutoload() {
define("localhost", "adrian");
}
}
All module bootstraps are run on every request.
If there is some processing you wish to perform when the request is routed to single module, then register a plugin - either in application bootstrap or in module bootstrap; as noted above, they will all run - that exits early if the request is not targeted at his module.
See this post by MWOP for further discussion:
http://www.mwop.net/blog/234-Module-Bootstraps-in-Zend-Framework-Dos-and-Donts.html
Im not sure..if i unserstand your question..you can try
public function dispatchLoopStartup(Zend_Controller_Request_Abstract $request)
{
$request = Zend_Controller_Front::getInstance()->getRequest();
if ('admin' == $request->getModuleName()) {
require_once APPLICATION_PATH.'/modules/admin/Bootstrap.php';
$moduleBootstrap = new Admin_Bootstrap();
$moduleBootstrap->bootstrap();
}
else if('site' == $request->getModuleName()){
$request->setModuleName('othermodule');
$request->setControllerName('othercontroller');
$request->setActionName('otherindex');
}
}