How to get module name using shardManager in Laminas? - php

I need to get the module name of dispatched request within Module.php onBootstrap method in a zf3 application. With the previous version (which was under Zend namespace) I was able to do that by using shared event manager as mentioned below.
public function onBootstrap(MvcEvent $e)
{
$app = $e->getApplication();
$eventManager = $app->getEventManager();
$sharedEventManager = $eventManager->getSharedManager();
$sharedEventManager->attach(AbstractActionController::class, MvcEvent::EVENT_DISPATCH, function($e) {
$controller = $e->getTarget();
$controllerClass = get_class($controller);
$moduleName = strtolower(substr($controllerClass, 0, strpos($controllerClass, '\\')));
// rest of code
// ..................
// ..................
}, 100);
}
But with the newer version of the framework (which is under Laminas namespace), it's not working. It's even not listening to the MvcEvent::EVENT_DISPATCH of the AbstractActionController::class target class. And I could found that they have removed the SharedEventManager functionality according to below link.
https://docs.laminas.dev/laminas-eventmanager/migration/removed/#sharedeventmanagerawareinterface
So the question is how to get the module name of the dispatched request within the onBootstrap method in new version of the framework?
Is there an any workaround to get module name ?

I don't know if this will help but to get the controller and module in a listener in Laminas I use.
In my module.php bootstrap method:
$eventManager = $event->getApplication()->getEventManager();
$eventManager->attach(\Laminas\Mvc\MvcEvent::EVENT_DISPATCH, array($this, 'onDispatch'), -100);
and in the onDispatch method:
public function onDispatch(\Laminas\EventManager\EventInterface $event)
{
$routeMatch = $event->getRouteMatch();
$controller = get_class($event->getTarget());
list($module) = explode("\\", $controller);
}
I hope this points you in the right direction.

Related

Adding events to Laminas

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.

ZF2 events for multiple modules

Currently I have an ZF2 application configured with the single module "application". I bootstrap the application an attach an event this way:
namespace Application;
use Zend\Mvc\ModuleRouteListener;
use Zend\Mvc\MvcEvent;
class Module
{
public function onBootstrap( MvcEvent $e)
{
$eventManager = $e->getApplication()->getEventManager();
$moduleRouteListener = new ModuleRouteListener();
$moduleRouteListener->attach( $eventManager);
$this->initTracking( $e);
}
/**
* Initialises user tracking check
* #param MvcEvent $e
*/
public function initTracking( MvcEvent $e)
{
$eventManager = $e->getApplication()->getEventManager();
$eventManager->attach( 'dispatch', function( $e){
$objTracking = new \Application\Event\Tracking( $e);
}, 200);
}
}
Now I need to create a new module "api", which should process only urls starting domain.com/api (I configure the router in "api" module config file to handle only such urls).
I bootstrap the "api" module the same way as "application" module, and I attach a dedicated event:
namespace Api;
use Zend\Mvc\ModuleRouteListener;
use Zend\Mvc\MvcEvent;
class Module
{
public function onBootstrap( MvcEvent $e)
{
$eventManager = $e->getApplication()->getEventManager();
$moduleRouteListener = new ModuleRouteListener();
$moduleRouteListener->attach( $eventManager);
$this->initLogging( $e);
}
/**
* Initialises loggging
* #param MvcEvent $e
*/
public function initLogging( MvcEvent $e)
{
$eventManager = $e->getApplication()->getEventManager();
$eventManager->attach( 'dispatch', function( $e){
$objLogger = new \Application\Event\Logging( $e);
}, 200);
}
}
What happens is that when I call domain.com/application - both modules are being initialised and events from both modules are being triggered. I need events to be triggered depending on the application which is dispatching the action.
How can I achieve that?
You are currently attaching the event listeners to the application event manager. This is a single event manager instance that will trigger all MVC events.
As it is the same instance it will make no difference where you attach the listeners; they will all be triggered regardless.
You will need to specifically check, in each listener, if the matched route is one that the listener should action. If it is not then exit out early.
For example:
public function onBootstrap(MvcEvent $event)
{
$eventManager = $event->getApplication()->getEventManager();
// There is no need to pass in the event
// to a seperate function as we can just attach 'initLogging' here
// as the event listener
$eventManager->attach('dispatch', array($this, 'initLogging'), 200);
}
// initLogging listener
public function initLogging(MvcEvent $event)
{
//... check the route is one you want
// this is quite basic to you might need to edit to
// suit your specific needs
$routeName = $event->getRouteMatch()->getMatchedRouteName();
if (false === strpos($routeName, 'api')) {
// we are not an api route so exit early
return;
}
$objLogger = new \Application\Event\Logging($event);
}
So the listener will still be triggered, however it won't 'do' anything.
You can however go further and prevent this unnecessary call by specifically targeting the required event manager that you are interested in; to do so we can use the SharedEventManager.
When attaching the listener to the SharedEventManager you need to provide an 'identifier' of the target event manager - I'll assume you are targeting a 'API controller'.
So the above would be changed to
public function onBootstrap(MvcEvent $event)
{
$application = $event->getApplication();
$sharedEventManager = $application->getEventManager()
->getSharedManager();
// The shared event manager takes one additional argument,
// 'Api\Controller\Index' is our target identifier
$eventManager->attach('Api\Controller\Index', 'dispatch', array($this, 'initLogging'), 200);
}
// initLogging listener
public function initLogging(MvcEvent $event)
{
// ... same bits we had before
}
the onDispatch method will be run in only one module
namespace Application;
use Zend\Http\PhpEnvironment\Request;
use Zend\Http\PhpEnvironment\Response;
use Zend\ModuleManager\Feature\ConfigProviderInterface;
use Zend\ModuleManager\ModuleManagerInterface;
use Zend\Mvc\MvcEvent;
/**
* #method Request getRequest()
* #method Response getResponse()
*/
class Module implements ConfigProviderInterface
{
public function getConfig()
{
return array_merge(
require __DIR__ . '/../config/module.config.php',
require __DIR__ . '/../config/router.config.php'
);
}
public function init(ModuleManagerInterface $manager)
{
$eventManager = $manager->getEventManager();
// Register the event listener method.
$sharedEventManager = $eventManager->getSharedManager();
$sharedEventManager->attach(__NAMESPACE__, MvcEvent::EVENT_DISPATCH,
[$this, 'onDispatch'], 100);
}
public function onDispatch(MvcEvent $e)
{
var_dump(__METHOD__);
}

Plugin for zend framework

$url = $_SERVER['SERVER_NAME'];
if(!filter_var($url, FILTER_VALIDATE_URL)){
return false;
}
return true;I need to connect plugin CheckDomain which loaded on pre dispatch with all modules except Admin.
Plugin is a class CheckDomain which could be called as a function CheckDomain() when it's called in that way it checks is domain equal to "test.example.com"
<?php
namespace Application\Controller\Plugin;
use Zend\Mvc\Controller\Plugin\AbstractPlugin;
use Zend\Mvc\Controller\Plugin\FlashMessenger;
use Zend\Mvc\Controller\Plugin\Forward;
use Zend\Mvc\Controller\Plugin\Layout;
use Zend\Mvc\Controller\Plugin\Params;
use Zend\Mvc\Controller\Plugin\PostRedirectGet;
use Zend\Mvc\Controller\Plugin\Redirect;
use Zend\Mvc\Controller\Plugin\Url;
use Zend\View\Model\ViewModel;
class CheckDomainPlugin extends AbstractPlugin{
public function checkdomain()
{
$url = $_SERVER['SERVER_NAME'];
if(!filter_var($url, FILTER_VALIDATE_URL)){
return false;
}
return true;
}
}
I call it for every controller except Admin, but I need to use it once.
I mean is it possible to load automatically plugin for all modules axcept admin
if you wish to "hook up" into every module you should read the zf2 documentation about MVC Events and the EventManager class.
http://framework.zend.com/manual/2.3/en/modules/zend.mvc.mvc-event.html#the-mvcevent
http://framework.zend.com/manual/2.3/en/modules/zend.event-manager.event-manager.html
Here is a small example for your Application/Module.php
public function onBootstrap(MvcEvent $e)
{
$application = $e->getApplication();
$serviceManager = $application->getServiceManager();
$eventManager = $application->getEventManager();
$sharedManager = $eventManager->getSharedManager();
// DISPATCH EVENT
$sharedManager->attach('Zend\Mvc\Controller\AbstractActionController', 'dispatch', function( MvcEvent $e) use ($serviceManager) {
$controller = $e->getTarget();
$controllerClass = get_class($controller);
$moduleNamespace = substr($controllerClass, 0, strpos($controllerClass, '\\'));
// this is the first segment from the module namespace
// if the Admin Namespace is something like this Admin/Controller/...
if( $moduleNamespace != 'Admin' ) {
$CheckDomainPlugin = $serviceManager->get('ControllerPluginManager')->get('CheckDomainPlugin');
// do something
}
}, 50 );
}
If you create a plugin, or any service, they will be available to all modules within the application. If there are areas in which the plugin should not be used then don't call it!
If I actually understand your problem; I would use an event listener for this. If you listen 'on dispatch' you can exclude all the admin controllers if you give them a unique interface.
// Module.php
public function onBootstrap($event)
{
$application = $event->getApplication();
$eventManager = $application->getEventManager()->getSharedManager();
$eventManager->attach(
'Zend\Mvc\Controller\AbstractActionController',
'dispatch',
function($e) {
$target = $e->getTarget(); // The dispatched controller
if ($controller instanceof AdminControllerInterface) {
return;
}
// Do something here
}
);
}

Modify ViewModel after controller dispatch

I'm trying to intercept the ViewModel, prior to it being rendered, and add it to another 'parent' view model - much like the way that the ZF2 layout wraps around the controllers returned content.
The simple (working) way to do this would be in each controller action.
public function dashboardAction() {
$dashboardContent = new ViewModel(array('foo' => 'bar'));
$parent = new ViewModel();
$parent->setTemplate('foo/bar/parent-template');
$parent->addChild($dashboardContent, 'content');
return $parent;
}
This works as expected and the 'child' view is correctly nested within the 'parent' in the final output.
As I have a number of controllers/actions that should all behave in the same way (resolved via their route name); I was hoping to encapsulate this in an event listener.
public function onBootstrap(MvcEvent $event)
{
$application = $event->getApplication();
$eventManager = $application->getEventManager();
$eventManager->attach(MvcEvent::EVENT_DISPATCH, array($this, 'addUserAccountLayout'), -100);
}
public function addUserAccountLayout(EventInterface $event)
{
$routeMatch = $event->getRouteMatch();
$controller = $event->getTarget();
$result = $event->getResult();
if (! $result instanceof ViewModel || $result instanceof JsonModel) {
return;
}
if (! $routeMatch instanceof RouteMatch || 0 !== strpos($routeMatch->getMatchedRouteName(), 'zfcuser') || $result->terminate()) {
return;
}
$application = $event->getApplication();
$serviceManager = $application->getServiceManager();
$accountService = $serviceManager->get('JobboardUser\Service\AccountService');
$user = $accountService->getCurrentUser();
$parent = new ViewModel();
$parent->setVariables(compact('user'));
$parent->setTemplate('jobboard-user/widget/user-account');
$parent->addChild($result, 'content');
$event->setResult($parent);
}
This however is not working; the normal view is rendered (without the parent). My guess is because I am either not using the correct event or event priority OR $event->setResult($view) is not the correct way to assign the result.
How can I modify and then re-assign the view from within an event listener?
I can't offer an explanation as to 'why', but a priority of -10 seems to be the sweet spot to get this working with the code you already have.
I do have one suggestion though, instead of listening to every dispatch event triggered by every module controller and then having to check if it's a zfcuser route, you can instead make use of the shared events manager to listen specifically to the ZfcUser controller you're interested in ...
public function onBootstrap(MvcEvent $event)
{
$application = $event->getApplication();
$eventManager = $application->getEventManager();
$sharedEvents = $eventManager->getSharedManager();
$sharedEvents->attach(
'ZfcUser\Controller\UserController', // controller FQCN to listen to
MvcEvent::EVENT_DISPATCH,
array($this, 'addUserAccountLayout'),
-10
);
}
If you do that, you can remove the routematch check 0 !== strpos($routeMatch->getMatchedRouteName(), 'zfcuser') in your callback entirely.
It's been a little while since I asked this; however I did find the solution I was after. It was indeed related to how the new ViewModel is attached back to the MvcEvent.
Previously I had tried to set the event's result property with no luck :
$mvcEvent->setResult($myNewViewModel);
The key was to first fetch the MvcEvent's own view model and then attach my custom one to it as a child.
$mvcEvent->getViewModel()->addChild($myNewViewModel, 'content');
I've also incorporated the point that #Crisp made regarding the event managers target. Previously, I was attaching to the main application dispatch event meaning that the listener would be called on every dispatch event.
I now specifically target the controllers I want to listen to.
$eventManager->attach(array(
'JobboardUser\Controller\AccountController',
'JobboardUser\Controller\ProfileController'
),
MvcEvent::EVENT_DISPATCH,
array($this, 'addUserAccountLayout'),
-80
);
The final listener code looks like this
public function addUserAccountLayout(EventInterface $event)
{
$result = $event->getResult();
if (! $result instanceof ViewModel || $result instanceof JsonModel) {
return;
}
$application = $event->getApplication();
$serviceManager = $application->getServiceManager();
$accountService = $serviceManager->get('JobboardUser\Service\AccountService');
$currentUser = $accountService->getCurrentUser();
$layout = new ViewModel();
$layout->setVariables(array('user' => $currentUser));
$layout->setTemplate('jobboard-user/widget/user-account');
$layout->addChild($result, 'userContent');
$event->getViewModel()->addChild($layout, 'content');
}

Zend Framework 2 actions without layout by default

I would like set all actions because when these return a ViewModel class, this has the parameter setTerminal = true by default.
I want that my aplication have this behavior because the 90% of my calls are AJAX.
Thanks in advance.
Check the Creating and Registering Alternate Rendering and Response Strategies.
http://framework.zend.com/manual/2.0/en/modules/zend.view.quick-start.html#creating-and-registering-alternate-rendering-and-response-strategies.
namespace Application;
class Module
{
public function onBootstrap($e)
{
// Register a "render" event, at high priority (so it executes prior
// to the view attempting to render)
$app = $e->getApplication();
$app->getEventManager()->attach('render', array($this, 'registerJsonStrategy'), 100);
}
public function registerJsonStrategy($e)
{
$app = $e->getTarget();
$locator = $app->getServiceManager();
$view = $locator->get('Zend\View\View');
$jsonStrategy = $locator->get('ViewJsonStrategy');
// Attach strategy, which is a listener aggregate, at high priority
$view->getEventManager()->attach($jsonStrategy, 100);
}
}

Categories