php event system implementation - php

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.

Related

Zend Framework 2 Event Manager

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)
{
}
}

(Laravel) Dynamic dependency injection for interface, based on user input

I am currently facing a very interesting dilemma with my architecture and implementation.
I have an interface called ServiceInterface which have a method called execute()
Then I have two different implementations for this interface: Service1 and Service2, which implements the execute method properly.
I have a controller called MainController and this controller has a "type-hint" for the ServiceInterface (dependency injection), it means that both, Service1 and Service2, can be called as resolution for that dependency injection.
Now the fun part:
I do not know which of those implementations to use (Service1 or Service2) because I just know if I can use one or other based on a user input from a previous step.
It means the user choose a service and based on that value I know if a can use Service1 or Service2.
I am currently solving the dependency injection using a session value, so depending of the value I return an instance or other, BUT I really think that it is not a good way to do it.
Please, let me know if you faced something similar and, how do you solve it, or what can I do to achieve this in the right way.
Thanks in advance. Please let me know if further information is required.
Finally, after some days of researching and thinking a lot about the best approach for this, using Laravel, I finally solved it.
I have to say that this was especially difficult in Laravel 5.2 because, in this version, the Session middleware only is executed in the controllers used in a route, it means that if for some reason I used a controller (not linked for a rote) and try to get access to the session it is not going to be possible.
So, because I cannot use the session, I decided to use URL parameters. Here you have the solution approach; I hope some of you found it useful.
so, you have an interface:
interface Service
{
public function execute();
}
Then a couple of implementations for the interface:
Service one:
class ServiceOne implements Service
{
public function execute()
{
.......
}
}
Service two.
class ServiceTwo implements Service
{
public function execute()
{
.......
}
}
The interesting part is that I have a controller with a function with a dependency with the Service interface. Still, I need to resolve it dynamically to ServiceOne or ServiceTwo based on user input. So:
The controller
class MyController extends Controller
{
public function index(Service $service, ServiceRequest $request)
{
$service->execute();
.......
}
}
Please note that ServiceRequest, validated that the request already have the parameter that we need to resolve the dependency (call it 'service_name')
Now, in the AppServiceProvider we can resolve the dependency in this way:
class AppServiceProvider extends ServiceProvider
{
public function boot()
{
}
public function register()
{
//This specific dependency is going to be resolved only if
//the request has the service_name field stablished
if(Request::has('service_name'))
{
//Obtaining the name of the service to be used (class name)
$className = $this->resolveClassName(Request::get('service_name')));
$this->app->bind('Including\The\Namespace\For\Service', $className);
}
}
protected function resolveClassName($className)
{
$resolver = new Resolver($className);
$className = $resolver->resolveDependencyName();
return $className;
}
}
So now all the responsibility is for the Resolver class. This class basically use the parameter passed to the constructor to return the full name (with namespace) of the class that is going to be used as an implementation of the Service interface:
class Resolver
{
protected $name;
public function __construct($className)
{
$this->name = $className;
}
public function resolveDependencyName()
{
//This is just an example, you can use whatever as 'service_one'
if($this->name === 'service_one')
{
return Full\Namespace\For\Class\Implementation\ServiceOne::class;
}
if($this->name === 'service_two')
{
return Full\Namespace\For\Class\Implementation\ServiceTwo::class;
}
//If none, so throw an exception because the dependency can not be resolved
throw new ResolverException;
}
}
Well, I really hope it helps some of you.
Best wishes!
---------- EDIT -----------
I just realize that it is not a good idea to use the request data directly inside the container of Laravel. It really is going to cause some trouble in the long term.
The best way is to directly register all the possible instances supported (serviceone and servicetwo) and then resolve one of them directly from a controller or a middleware, so then is the controller "who decides" what service to use (from all the available) based on the input from the request.
In the end, it works at the same, but it is going to allow you to work more naturally.
I have to say thanks to rizqi, a user from the questions channel of the slack chat of Laravel.
He personally created a golden article about this. Please read it because it solves this issue completely and in a very right way.
laravel registry pattern
The fact that you define that your controller works with ServiceInterface is ok
If you have to choose the concrete implementation of the service basing on a previous step (that, as i've understood, happens in a previous request) storing the value in session or in database is right too, as you have no alternative: to choose the implementation you have to know the value of the input
The important point is to 'isolate' the resolution of the concrete implementation from the input value in one place: for example create a method that takes this value as a parameter and returns the concrete implementation of the service from the value:
public function getServiceImplementation($input_val)
{
switch($input_val)
{
case 1 : return new Service1();
case 2 : return new Service2();
}
}
and in your controller:
public function controllerMethod()
{
//create and assign the service implementation
$this->service = ( new ServiceChooser() )->getServiceImplementation( Session::get('input_val') );
}
In this example i've used a different class to store the method, but you can place the method in the controller or use a Simple Factory pattern, depending on where the service should be resolved in your application
It's an interesting problem. I'm currently using Laravel 5.5 and have been mulling it over. I also want my service provider to return a specific class (implementing an interface) based upon user input. I think it's better to manually pass the input from the controller so it's easier to see what's going on. I would also store the possible values of the class names in the config.
So based upon the Service classes and interface you've defined above i came up with this:
/config/services.php
return [
'classes': [
'service1' => 'Service1',
'service2' => 'Service2',
]
]
/app/Http/Controllers/MainController.php
public function index(ServiceRequest $request)
{
$service = app()->makeWith(ServiceInterface::class, ['service'=>$request->get('service)]);
// ... do something with your service
}
/app/Http/Requests/ServiceRequest.php
public function rules(): array
$availableServices = array_keys(config('services.classes'));
return [
'service' => [
'required',
Rule::in($availableServices)
]
];
}
/app/Providers/CustomServiceProvider.php
class CustomServiceProvider extends ServiceProvider
{
public function boot() {}
public function register()
{
// Parameters are passed from the controller action
$this->app->bind(
ServiceInterface::class,
function($app, $parameters) {
$serviceConfigKey = $parameters['service'];
$className = '\\App\\Services\\' . config('services.classes.' . $serviceConfigKey);
return new $className;
}
);
}
}
This way we can validate the input to ensure we are passing a valid service, then the controller handles passing the input from the Request object into the ServiceProvider. I just think when it comes to maintaining this code it will be clear what is going on as opposed to using the request object directly in the ServiceProvider.
PS Remember to register the CustomServiceProvider!
I find the best way to deal with this is using a factory pattern. You can create a class say ServiceFactory and it has a single method create() it can accept an argument which is used to dynamically choose which concrete class to instantiate.
It has a case statement based on the argument.
It will use App::make(ServiceOne::class) or App::make(ServiceTwo::class).depending on which one is required.
You are then able to inject this into your controller (or service which depends on the factory).
You can then mock it in a service unit test.
Recently, I had to implement a similar logic where I was to implement a method to perform mobile top-ups for multiple networks in our application. So, I decided to implement the logic using Factory and Bridge pattern. Factory to create an instance of the concrete Service class based on the user input, and then, the Bridge pattern to set closely related classes into separate hierarchies and route the request to the respective class.
In the controller's method, both Factory and Service classes are injected. The TopUpServiceFactory's create method creates an object of the concrete class. The TopUpService class then routes the request to that concrete class method.
class TopUpController extends Controller
{
public function topUp(Request $request, TopUpServiceFactoryInterface $serviceFactory, TopUpServiceInterface $topUpService)
{
$serviceFactory->create($request->networkCode);
$topUpService->TopUp($request->all());
}
}
The TopUpServiceFactoryInterface and TopUpServiceInterface are bound to TopUpServiceFactory and TopUpService concrete Classes respectively in Service Container.
class AppServiceProvider extends ServiceProvider
{
public function register()
{
$this->app->bind(TopUpServiceFactoryInterface::class, TopUpServiceFactory::class);
$this->app->bind(TopUpServiceInterface::class, TopUpService::class);
}
}
The create method accepts user input and creates an object of the respective class based on the user input.
class TopUpServiceFactory implements TopUpServiceFactoryInterface
{
public function create(string $networkCode)
{
switch ($networkCode) {
case 'network1':
app()->bind(NetworkServiceInterface::class, Network1Service::class);
break;
case 'network2':
app()->bind(NetworkServiceInterface::class, Network2Service::class);
break;
default:
app()->bind(NetworkServiceInterface::class, DefaultNetworkService::class);
break;
}
}
}
The Service Class then picks the object of NetworkService Class and forwards the request.
class TopUpService implements TopUpServiceInterface
{
public function topUp(array $requestParams)
{
$networkService = app()->get(NetworkServiceInterface::class);
$networkService->topUp($requestParams);
}
}
All network's concrete classes implement a common interface NetworkServiceInterface, which is used to inject dependency dynamically, implementing Liskov Substitution Principle
class Network1Service implements NetworkServiceInterface
{
public function topUp(array $requestParam)
{
Process Topup ......
}
}
class Network2Service implements NetworkServiceInterface
{
public function topUp(array $requestParam)
{
Process Topup ......
}
}
...

Call a function on each http request

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.

How to use events in yii2?

I've been through the documentation and all the articles on Yii2 events found using Google. Can someone provide me a good example of how events can be used in Yii2 and where it may seem logical?
I can explain events by a simple example. Let's say, you want to do few things when a user first registers to the site like:
Send an email to the admin.
Create a notification.
you name it.
You may try to call a few methods after a user object is successfully saved. Maybe like this:
if($model->save()){
$mailObj->sendNewUserMail($model);
$notification->setNotification($model);
}
So far it may seem fine but what if the number of requirements grows with time? Say 10 things must happen after a user registers himself? Events come handy in situations like this.
Basics of events
Events are composed of the following cycle.
You define an event. Say, new user registration.
You name it in your model. Maybe adding constant in User model. Like const EVENT_NEW_USER='new_user';. This is used to add handlers and to trigger an event.
You define a method that should do something when an event occurs. Sending an email to Admin for example. It must have an $event parameter. We call this method a handler.
You attach that handler to the model using its a method called on(). You can call this method many times you wish - simply you can attach more than one handler to a single event.
You trigger the event by using trigger().
Note that, all the methods mentioned above are part of Component class. Almost all classes in Yii2 have inherited form this class. Yes ActiveRecord too.
Let's code
To solve above mentioned problem we may have User.php model. I'll not write all the code here.
// in User.php i've declared constant that stores event name
const EVENT_NEW_USER = 'new-user';
// say, whenever new user registers, below method will send an email.
public function sendMail($event){
echo 'mail sent to admin';
// you code
}
// one more hanlder.
public function notification($event){
echo 'notification created';
}
One thing to remember here is that you're not bound to create methods in the class that creates an event. You can add any static, non static method from any class.
I need to attach above handlers to the event . The basic way I did is to use AR's init() method. So here is how:
// this should be inside User.php class.
public function init(){
$this->on(self::EVENT_NEW_USER, [$this, 'sendMail']);
$this->on(self::EVENT_NEW_USER, [$this, 'notification']);
// first parameter is the name of the event and second is the handler.
// For handlers I use methods sendMail and notification
// from $this class.
parent::init(); // DON'T Forget to call the parent method.
}
The final thing is to trigger an event. Now you don't need to explicitly call all the required methods as we did before. You can replace it by following:
if($model->save()){
$model->trigger(User::EVENT_NEW_USER);
}
All the handlers will be automatically called.
For "global" events.
Optionally you can create a specialized event class
namespace your\handler\Event\Namespace;
class EventUser extends Event {
const EVENT_NEW_USER = 'new-user';
}
define at least one handler class:
namespace your\handler\Event\Namespace;
class handlerClass{
// public AND static
public static function handleNewUser(EventUser $event)
{
// $event->user contain the "input" object
echo 'mail sent to admin for'. $event->user->username;
}
}
Inside the component part of the config under (in this case) the user component insert you event:
'components' => [
'user' => [
...
'on new-user' => ['your\handler\Event\Namespace\handlerClass', 'handleNewUser'],
],
...
]
Then in your code you can trigger the event:
Yii::$app->user->trigger(EventUser::EVENT_NEW_USER, new EventUser($user));
ADD
You can also use a closure:
allows IDE to "detect" the use of the function (for code navigation)
put some (small) code that manage the event
example:
'components' => [
'user' => [
...
'on new-user' => function($param){ your\handler\Event\Namespace\handlerClass::handleNewUser($param);},
'on increment' => function($param){ \Yii::$app->count += $param->value;},
],
...
]
By default Yii2 already provide some event declaration, You can read more about explanation on BaseActiveRecord.
You can use this variable as same as declaring it manually.
public function init()
{
parent::init();
$this->on(self::EVENT_AFTER_INSERT, [$this, 'exampleMethodHere']);
}

Where to register event listeners

I'm trying to use the Event System in CakePHP v2.1+
It appears to be quite powerful, but the documentation is somewhat vague. Triggering the event seems pretty straight-forward, but I'm not sure how to register the corresponding listener(s) to listen for the event. The relevant section is here and it offers the following example code:
App::uses('CakeEventListener', 'Event');
class UserStatistic implements CakeEventListener {
public function implementedEvents() {
return array(
'Model.Order.afterPlace' => 'updateBuyStatistic',
);
}
public function updateBuyStatistic($event) {
// Code to update statistics
}
}
// Attach the UserStatistic object to the Order's event manager
$statistics = new UserStatistic();
$this->Order->getEventManager()->attach($statistics);
But it does not say where this code should reside. Inside a specific controller? Inside the app controller?
In case it's relevant, the listener will be part of a plugin which I am writing.
Update:
It sounds like a popular way to do this is by placing the listener registration code in the plugin's bootstrap.php file. However, I can't figure out how to call getEventManager() from there because the app's controller classes, etc aren't available.
Update 2:
I'm also told that listeners can live inside Models.
Update 3:
Finally some traction! The following code will successfully log an event when inside of my MyPlugin/Config/bootstrap.php
App::uses('CakeEventManager', 'Event');
App::uses('CakeEventListener', 'Event');
class LegacyWsatListener implements CakeEventListener {
public function implementedEvents() {
return array(
'Controller.Attempt.complete' => 'handleLegacyWsat',
);
}
public static function handleLegacyWsat($event) { //method must be static if used by global EventManager
// Code to update statistics
error_log('event from bootstrap');
}
}
CakeEventManager::instance()->attach(array('LegacyWsatListener', 'handleLegacyWsat'), 'Controller.Attempt.complete');
I'm not sure why, but I can't get errors when I try to combine the two App::uses() into a single line.
Events
Events are callbacks that are associated to a string. An object, like a Model will trigger an event using a string even if nothing is listening for that event.
CakePHP comes pre-built with internal events for things like Models. You can attach an event listener to a Model and respond to a Model.beforeSave event.
The EventManager
Every Model in Cake has it's own EventManager, plus there is a gobal singleton EventManager. These are not all the same instance of EventManager, and they work slightly differently.
When a Model fires an event it does so using the EventManager reference it has. This means, you can attach an event listener to a specific Model. The advantages are that your listener will only receive events from that Model.
Global listeners are ones attached to the singleton instance of EventManager. Which can be accessed anywhere in your code. When you attach a listener there it's called for every event that happens no matter who triggers it.
When you attach event listener in the bootstrap.php of an app or plugin, then you can use the global manager, else you have to get a reference to the Model you need using ClassRegistry.
What EventManager To Use?
If the event you want to handle is for a specific Model, then attach the listener to that Model's EventManager. To get a reference of the model you can call the ClassRegistry::init(...).
If the event you want to handle could be triggered anywhere, then attach the listener to the global EventManager.
Only you know how your listener should be used.
Inside A Listener
Generally, you put your business logic into models. You shouldn't need to access a Controller from an event listener. Model's are much easier to access and use in Cake.
Here is a template for creating a CakeEventListener. The listener is responsible for monitoring when something happens, and then passing that information along to another Model. You should place your business logic for processing the event in Models.
<?php
App::uses('CakeEventListener', 'Event');
class MyListener implements CakeEventListener
{
/**
*
* #var Document The model.
*/
protected $Document;
/**
* Constructor
*/
public function __construct()
{
// get a reference to a Model that we'll use
$this->Document = ClassRegistry::init('Agg.Document');
}
/**
* Register the handlers.
*
* #see CakeEventListener::implementedEvents()
*/
public function implementedEvents()
{
return array(
'Model.User.afterSave'=>'UserChanged'
);
}
/**
* Use the Event to dispatch the work to a Model.
*
* #param CakeEvent $event
* The event object and data.
*/
public function UserChanged(CakeEvent $event)
{
$data = $event->data;
$subject = $event->subject();
$this->Document->SomethingImportantHappened($data,$subject);
}
}
What I like to do is place all my Events into the Lib folder. This makes it very easy to access from anywhere in the source code. The above code would go into App/Lib/Event/MyListener.php.
Attaching The EventListeners
Again, it depends on what events you need to listen for. The first thing you have to understand is that an object must be created in order to fire the event.
For example;
It's not possible for the Document model to fire Model.beforeSave event when the Calendar controller is displaying an index, because the Calendar controller never uses the Document model. Do you need to add a listener to Document in the bootstrap.php to catch when it saves? No, if Document model is only used from the Documents controller, then you only need to attach the listener there.
On the other hand, the User model is used by the Auth component almost every. If you want to handle a User being deleted. You might have to attach an event listener in the bootstrap.php to ensure no deletes sneak by you.
In the above example we can attach directly to the User model like so.
App::uses('MyListener','Lib');
$user = ClassRegistry::init('App.User');
$user->getEventManager()->attach(new MyListener());
This line will import your listener class.
App::uses('MyListener','Lib');
This line will get an instance of the User Model.
$user = ClassRegistry::init('App.User');
This line creates a listener, and attaches it to the User model.
$user->getEventManager()->attach(new MyListener());
If the User Model is used in many different places. You might have to do this in the bootstrap.php, but if it's only used by one controller. You can place that code in the beforeFilter or at the top of the PHP file.
What About Global EventManager?
Assuming we need to listen for general events. Like when ever any thing is saved. We would want to attach to the global EventManager. It would go something like this, and be placed in the bootstrap.php.
App::uses('MyListener','Lib');
CakeEventManager::instance()->attach(new MyListener());
If you want to attach an event listener inside bootstrap.php file of your plugin, everything should work fine using the hints posted in the answers. Here is my code (which works properly):
MyPlugin/Config/bootstrap.php:
App::uses('CakeEventManager', 'Event');
App::uses('MyEventListener', 'MyPlugin.Lib/Event');
CakeEventManager::instance()->attach(new MyEventListener());
MyPlugin/Lib/Event/MyEventListener.php:
App::uses('CakeEventListener', 'Event');
class MyEventListener implements CakeEventListener {
...
}
Event listeners related to MyPlugin are being registered only when the plugin is loaded. If I don't want to use the plugin, event listeners are not attached. I think this is a clean solution when you want to add some functionality in various places in your app using a plugin.
Its' not important, where the code resides. Just make sure its being executed and your events are properly registered & attached.
We're using a single file where all events are attached and include it from bootstrap.php, this ensures that all events are available from all locations in the app.
The magic happens when you dispatch an event, like from an controller action.
$event = new CakeEvent('Model.Order.afterPlace', $this, array('some'=>'data') ));
$this->getEventManager()->dispatch($event);
However, you can dispatch events from anywhere you can reach the EventManager (in Models, Controller and Views by default)

Categories