Laravel 5.0 - Event Annotations (removed)

Posted on October 10, 2014 | By Matt Stauffer


Warning: This post is over a year old. I don't always update old posts with new information, so some of this information may be out of date.

Note: Event Annotations were eventually removed from core, and separated to a package maintained by the Laravel Community. The package should function the same as the documentation here, other than that it requires binding a custom service provider. Feedback can go to the Github issues for the project or to @artisangoose in the Larachat slack.

In 5.0, Laravel is moving more and more of the top-level, bootstrapped, procedural bindings and definitions into a more Object-Oriented, separation-of-concerns-minded structure. Filters are now objects, controllers are now namespaced, the PSR-4-loaded application logic is now separate from the framework configuration, and more.

We saw in the last post that annotations are one of the ways Laravel 5.0 is making this change. Where routes used to be bound one after another in routes.php, they now can be bound with annotations on the controller class and method definitions.

Setting the stage

Another part of Laravel that has traditionally been bound with a list of calls one after another is event listeners, and this is the next target of the annotation syntax.

Consider the following code:

Event::listen('user.signup', function($user)
{
    $intercom = App::make('intercom');
    $intercom->addUser($user);
});

Somewhere in your code—in a service provider, maybe, or maybe just in a global file somewhere—you've bound a listener (the closure above) to the "user.signup" event.

Of course, you're probably noticing that all that closure does is call a single method—so we could refactor it to this:

Event::listen('user.signup', 'Intercom@addUser');

Introducing Event Annotations

Now, let's drop the need for the binding entirely, and replace it with an annotation.

<?php namespace App;

class Intercom
{
    /**
     * @Hears("user.signup")
     */
    public function addUser(User $user)
    {
        return $this->api_wrapper->sendSomeAddThing(
            $user->email,
            $user->name
        );
    }
}

As you can see, the @Hears annotation can take a string event name, but it can also take an array of event names (in annotations, arrays are surrounded by {} instead of []).

You also have to add the name of your classes to the $scan property on the EventServiceProvider. So, open up App/Providers/EventServiceProvider.php, find the $scan array, and update it:

<?php
...
    protected $scan = [
        'App\Intercom'
    ];

Now, run artisan event:scan and you'll get a file named storage/framework/events.scanned.php, with the following contents:

<?php

$events->listen(array (
  0 => 'user.signup',
), 'App\Intercom@addUser');

Instantly bound.

Conclusion

There are positives and negatives to working with your event system this way.

The primary negative I see is that you could look at this annotation as being framework-specific; if that's the case, you're now placing framework-specific code directly into your domain. If you imagine this Intercom class being something you're passing around between several sites, its binding may be specific to this site--in which case you'd be better off using the classic style of binding. However, that's not always the case.

Note that this negative is different from the same situation in Route Annotations, which are only being applied to Controllers--which are not domain objects.

The positives I can see at first glance are that first, you're defining the method's act of listening on the method itself, rather than elsewhere; and second, that you're defining the listener in a way that it can be programmatically accessed (meaning you could, at any point, replace artisan event:scan with a program of your own devising that outputs something other than a Laravel events.scanned file). There are likely smarter folks than me that'll weigh in on this.


Comments? I'm @stauffermatt on Twitter


Tags: laravel  •  5.0  •  laravel 5