I'm trying to configure the finish function for module.php in zend, from what I understand you need to configure some sort of listener (in bootstrap I think) that will call the finish function and I can then execute code after its finished with the user request.
Can someone provide some example code to setup the module to call finish once it has finished the user request.
Thanks!
You can do this in the onBootstrap method of your Module.php as the following:
public function onBootstrap(MvcEvent $e)
{
$em = $e->getApplication()->getEventManager();
$em->attach(\Zend\Mvc\MvcEvent::EVENT_FINISH, array($this, 'doSomething'));
}
and then define a function that doSomething in your Module.php as the following:
public function doSomething(MvcEvent $e)
{
// your code goes here
}
You can also add some priority for the callback functions you want to execute if you attached more than one listener on the same event as the following:
$em->attach(\Zend\Mvc\MvcEvent::EVENT_FINISH, array($this, 'doSomethingFirst'), 20);
$em->attach(\Zend\Mvc\MvcEvent::EVENT_FINISH, array($this, 'doAnotherThingLater'), 10);
Higher priority values execute earliest. (Default priority is 1, and negative priorities are allowed.)
The basic idea is to attach a listener to the event, as you've rightly noted the place to do that is in the onBootstrap method of your Module class. The following should get you started...
public function onBootstrap(MvcEvent $e)
{
$e->getApplication()->getEventManager()->attach(MvcEvent::EVENT_FINISH, function ($e) {
// do something...
});
}
Related
I am learning about zf2 events and below is my code:
module.php
public function init(ModuleManagerInterface $managers) {
$eventManager = $managers->getEventManager();
/* $eventManager->attach('do', array($this, function ($e) {
$event = $e->getName();
$params = $e->getParams();
printf(
'Handled event "%s", with parameters %s',
$event,
json_encode($params)
);
})
); */
$eventManager->attach('do', array($this, 'demoEvent') );
}
public function demoEvent(Event $e) {
echo ' in demo Event';
}
and in controller function i have triggered the event.
$this->getEventManager()->trigger('do', $this, ['aman', 'deep']);
but call to demoEvent action is never made. Even i tried using closure as you can see above but it gives me "Invalid callback provided" Exception.
What i am doing wrong. Can someone help me in understanding Event Manager better. Thanks
Your approach is almost correct. The problem is that you are attaching the event listener "demoEvent" to the application event manager, rather than the controller's event manager.
As the controller, assuming that it extends AbstractActionController, will also be capable of triggering it's own events.
You therefore need to update the way you attach the listener to ensure it is registered with the correct event manager.
There are a few options.
Attach event listeners inside the controller factory. You can call $controller->getEventManager()->attach(); inside the factory so when the controller is created the event listener is always attached.
Override the attachDefaultListeners() defined in the AbstractActionController this will automatically be called when the controller is initialized by the controller manager. This provides access to the controllers event manager, just be sure to remember to call parent::attachDefaultListeners().
Lastly, you can use the "Shared Event Manager" which is really just a proxy to the target event manager (and despite its name, not an event manager). This allows you to only slightly modify the code you have written and keep event listener registration independent of the triggering context (controller).
For example.
class Module
{
public function onBootstrap(MvcEvent $mvcEvent)
{
$sharedManager = $mvcEvent->getEventManager()->getSharedManager();
$sharedManager->attach(
'Foo\\Controller\\BarController', // Event manager 'identifier', which one we want
'do' // Name of event to listen to
[$this, 'demoEvent'], // The event listener to trigger
1, // event priority
);
}
public function demoEvent($event)
{
}
}
In my Apigility project I have different Rest resources, all of them extends my class ResourseAbstract and in there I extend the AbstractResourceListener as Apigility needs.
So for example my resource User:
<?php
namespace Marketplace\V1\Rest\User;
use ZF\ApiProblem\ApiProblem;
use Marketplace\V1\Abstracts\ResourceAbstract;
class UserResource extends ResourceAbstract
{
public function fetch($id)
{
$result = $this->getUserCollection()->findOne(['id'=>$id]);
return $result;
}
}
And ResourceAbstract:
<?php
namespace Marketplace\V1\Abstracts;
use ZF\Rest\AbstractResourceListener;
use Zend\ServiceManager\ServiceLocatorAwareInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
use ZF\ApiProblem\ApiProblem;
class ResourceAbstract extends AbstractResourceListener implements ServiceLocatorAwareInterface {
}
Now, I need to run a function each time an http request is made, if I query /user in my browser the UserResource class will get instantiated and so the ResourceAbstract, my "solution" to get something to run on each call was to use a constructor inside ResourceAbstract, and this "works":
function __construct() {
$appKey = isset(getallheaders()['X-App-Key']) ? getallheaders()['X-App-Key'] : null;
$token = isset(getallheaders()['X-Auth-Token']) ? getallheaders()['X-Auth-Token'] : null;
//some code
return new ApiProblem(400, 'The request you made was malformed');
}
The thing is I need to return an ApiProblem in some cases (bad headers on the http request), but as you know constructor function does not return parameters. Another solution will be to thrown an exception but in Apigility you are supposed to ise ApiProblem when there is an api problem. Is the constructor approach correct? How will you solve this?
Throwing an exception would be a solution, as long as you catch it on the parent portion of the code.
Are you using the ZEND MVC with your apigility project ?
If yes, you could consider hooking up a call that will be executed before the MVC does the dispatching.
If you want to look on the feasability of that approach, you can check that question asked on stackoverflow : Zend Framework 2 dispatch event doesn't run before action
I've not used this library, however it looks as if you can attach a listener to 'all' events by either extending the 'dispatch' method or adding your own event listener with high priority. The controller then listens for the returned 'ApiProblem'.
Attaching a listener is probably a better idea, in your custom class extending AbstractResourceListener (or from within it's service factory) you can then attach the event.
abstract class MyAbstractResource extends AbstractResourceListener
{
public function attach(EventManagerInterface $eventManager)
{
parent::attach($eventManager);
$eventManager->attach('*', [$this, 'checkHeaders'], 1000);
}
public function checkHeaders(EventInterface $event)
{
$headers = getallheaders();
if (! isset($headers['X-App-Key'])) {
return new ApiProblem(400, 'The request you made was malformed');
}
if (! isset($headers['X-Auth-Token'])) {
return new ApiProblem(400, 'The request you made was malformed');
}
}
}
The above would mean that any event triggered would first check if the headers are set, if not a new ApiProblem is returned.
I would like to implement an Event system in my custom MVC framework, to allow decoupling
of classes that need to interact with each other. Basically, the ability for any class to trigger an event and any other class that listens for this event to be able to hook into it.
However, I cannot seem to find a correct implementation given the nature of php's share nothing architecture.
For instance, let's say that I have a User model that each time that it is updated, it triggers a userUpdate event. Now, this event is useful for class A (for instance) as it needs to apply its own logic when a user is updated.
However, class A is not loaded when a user is updated, so it cannot bind to any events triggered by the User object.
How can you get around such a scenario?
Am I approaching it wrongly?
Any ideas would be greatly appreciated
There must be an instance of class A before the event is triggered because you must register for that event. An exception would be if you'd register a static method.
Let's say you have an User class which should trigger an event. First you need an (abstract) event dispatcher class. This kind of event system works like ActionScript3:
abstract class Dispatcher
{
protected $_listeners = array();
public function addEventListener($type, callable $listener)
{
// fill $_listeners array
$this->_listeners[$type][] = $listener;
}
public function dispatchEvent(Event $event)
{
// call all listeners and send the event to the callable's
if ($this->hasEventListener($event->getType())) {
$listeners = $this->_listeners[$event->getType()];
foreach ($listeners as $callable) {
call_user_func($callable, $event);
}
}
}
public function hasEventListener($type)
{
return (isset($this->_listeners[$type]));
}
}
Your User class can now extend that Dispatcher:
class User extends Dispatcher
{
function update()
{
// do your update logic
// trigger the event
$this->dispatchEvent(new Event('User_update'));
}
}
And how to register for that event? Say you have class A with method update.
// non static method
$classA = new A();
$user = new User();
$user->addEventListener('User_update', array($classA, 'update'));
// the method update is static
$user = new User();
$user->addEventListener('User_update', array('A', 'update'));
If you have proper autoloading the static method can be called.
In both cases the Event will be send as parameter to the update method. If you like you can have an abstract Event class, too.
I made a very simple PHP Event Dispatcher / Event Hander for myself, it is testable and has been used on my websites.
If you need it, you can take a look.
We are using zend framework 2 for a new application, i would like to have the same logging system of Rails or similar, i would like have a log for each request, is possible to do this in Zend?
It depends what you want to log. If it is just an access log, you should try to use the webserver's log. The logs from Apache/nginx/IIS etc perform better than you will achieve in your ZF2 app.
If you need to log inside the ZF2 application, you have two choices. First option is at bootstrap. It's one of the earliest options you can use, so probably therefore the best. However, you can also look at route or dispatch. Those two events are called during the "run" phase of the application. With these events, you have for example a route match available and therefore you know (or not) if your request did match any controller (or in case you don't have the match, it's a 404).
Some examples. Let's assume you have a logger configured in the ServiceManager under the logger key. Then to log at bootstrap:
namespace Application;
class Module
{
public function onBootstrap($e)
{
$app = $e->getApplication();
$sm = $app->getServiceManager();
$logger = $sm->get('logger');
$logger->debug('Log here!');
}
}
Or for example if you wait for route, you attach a listener for the route event:
namespace Application;
use Zend\Mvc\MvcEvent;
use Zend\Mvc\Router\RouteMatch;
class Module
{
public function onBootstrap($e)
{
$app = $e->getApplication();
$em = $app->getEventManager();
$sm = $app->getServiceManager();
$logger = $sm->get('logger');
$em->attach(MvcEvent::EVENT_ROUTE, function($e) use ($logger) {
$match = $e->getRouteMatch();
// No route, this is a 404
if (!$match instanceof RouteMatch) {
return;
}
$logger->debug(sprintf(
'Route event with route %s',
$match->getMatchedRouteName()
));
});
}
}
Sounds like you could attach a listener to the dispatch event on Zend\Mvc\Application.
For reference, Rob Allen has created a handy list of ZF2 events.
namespace Auth;
use Zend\ModuleManager\ModuleManager;
class Module
{
public function init(ModuleManager $moduleManager)
{
$sharedEvents = $moduleManager->getEventManager()->getSharedManager();
$sharedEvents->attach(__NAMESPACE__, 'dispatch', function($e) {
echo "I am init module dispatch";
exit();
}, 100);
}
}
$moduleManager->getEventManager()->getSharedManager()->attach() is working fine in ZF2 BETA5 but it is not working in stable final release.
Has this functionality taken off in final release?
How can I make this work in ZF2 final release?
public function onBootstrap(MvcEvent $e)
{
$application = $e->getApplication();
$sharedManager = $application->getEventManager()->getSharedManager();
$sharedEvents->attach(__NAMESPACE__, 'dispatch', function($e) {
echo "I am init module dispatch";
exit();
}, 100);
}
In Beta Series of zend framework2
Auth\src\User\Controller\UserController.php
but in final release of zf2 this does not work. Main namespace folder should match exactly same as under src folder. so above will work only like this
Auth\src\Auth\Controller\UserController.php
or
User\src\User\Controller\UserController.php
Don't forget to change your namespaces and paths in module.php and module.config.php and controller file.
There are two ways,
You can get it from Module.php init method, by passing ModuleManger object into it and then modulemanager->getEventManager.
Or from onBootstrap method again in Module.php but not from ModuleManager but by the application object as Abdul did.
Remember, init and onBoostrap methods run for every page request. Registering Event there is okay but do not put heavy stuff there. I prefer sharedEventManager, as it is available even if the service is initializes in future.
Cheers!