Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
243 views
in Technique[技术] by (71.8m points)

php - 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.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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());

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...