CakePHP event listening

Posted on June 14, 2012

Recently I’ve been working on a new site for a large corporation. I’m building it in CakePHP, which is a bit of a far cry from the Zend and Symfony work that I’ve been doing as of late. Overall it’s been a pretty positive experience, save for the few quirks that come with using Cake. For example, filtering with Containable is a mess that doesn’t really work; Linkable is a much, much better solution. Why they haven’t rolled that into the actual framework is beyond me.

Anyway, one part of what I’m building uses the new event listening system that they built into Cake 2.1. It’s a good idea, but their site is woefully lacking in details on how to use it. From what I can gather, you basically have to make sure to use or import every single class that has a listener on it, which is sort of cumbersome. So let’s say I have a class called Article that has a listener on it. Unless somewhere along the line I explicitly state App::uses(‘Article’), it’ll never get called. Now their documentation says that you can write listener classes as a way to sort of break out the logic. Makes more sense – but you still need to include all those listeners.

Just creating app/Event and tossing in all the listener classes didn’t work, so I came up with this solution:

// in AppController
    public function __construct($request = null, $response = null) {
        parent::__construct($request, $response);
 
        $this->loadEvents($request, $response);
    }
 
    /**
     * Loads all the events in the Event folder.
     * Unfortunately there isn't really a better way to do this in Cake.
     */
    public function loadEvents($request, $response) {
 
        // get all PHP files in app/Event
        foreach(glob(APP."Event/*.php") as $eventFile) {
 
            // get only the class name
            $className = str_replace(".php", "", basename($eventFile));
 
            // use the file
            App::uses($className, 'Event');
 
            // then instantiate the file and attach it to the event manager
            $this->getEventManager()->attach(new $className($request, $response));
        }
 
    }

It’s not ideal, but it does work. One advantage to this solution is that it gives the event classes access to the request (and response) objects, which they normally don’t have. I’m not sure how this is going to work with plugins, though, so that’s the next step. And maybe this can be turned into a component? More updates as they come.

4 responses to “CakePHP event listening”

  1. Jose Lorenzo says:

    Hey, very good blog post. It is evident that your are just starting with CakePHP and it is good to know it’s been a positive experience so far. I wanted to give you some insight on the concerns you’ve raised in your article.

    Usually containable is a bit more powerful than linkable, although linkable is a lot more flexible since it lets you join any model at will in the same query. This, of course, makes it harder to use since you need to think in terms of the SQL instead of just relationships. I think that is one of the few reasons because it was not added to the core. I’m not sure what your problems with Containable are, which I know they exist, but in most cases you can use that behavior without much hassle.

    On The event dispatching thing, documentation is lacking details on purpose. If you want to implement the observer pattern, then you may have a very good reason to do so and usually that is the case for people who have a plan in mind on wich events to trigger or how listeners should interact with the system. As you figured out, there are easy alternatives as the one you have just built! In most cases people will only need default callbacks and can use default automatic classes such as components, behaviors, helpers or just use the callbacks in the controller, or models.

    App::uses() is an autoloader hint, you need it in order to instantiate classes without having to require the file yourself. You can also come up with your own autoloader and register it with spl functions. That seems to be what some people is doing for magic event listeners instantiation.

    I’m not sure what you are after with attaching events dynamically to a controller, but I’m sure you have good reasons. As an advise I would cache the calls to glob() so they don’t become a performance problem in the long run. Thanks again for your article!

  2. Greg says:

    Hey! Thanks much for your comment.

    My containable problem was basically this one – I was filtering by a relation three deep (A -> B -> C where c.name = ‘foo’; all three are belongsTo relationships), and it wasn’t working. Linkable handled it just fine. Not a big deal overall.

    I get what you were saying with the event dispatcher. Honestly I would have liked to have seen at least a little more about event dispatching before setting out on my own, so hopefully this post helps to pay it forward a bit, heh. And I guess it’d be nice for Cake to have a main way forward to handle that sort of thing so that everyone uses it the best/cleanest way possible. But you’re right in that it’s really up to people to build what’s best for them.

    Anyway, I appreciate your comment on here. Thanks for stopping by!

  3. […] my last foray into events in CakePHP, I’ve made a bit more headway into events. The challenge this time was to create a listener […]

  4. Martin says:

    Nice article! But since Cake 2 there is an even simply method to access request object from anywhere:

    Router::getRequest();

    It doesn’t solve your problem with the response object because there are not methods in the Router class to get the response object. But if you only want the request object (and in many cases you only need the request object), use Router::getRequest()!

    Regards,
    Martin

Leave a Reply

Your email address will not be published. Required fields are marked *