Laravel 5.0 - Event Annotations (removed)

(This is part of a series of posts on New Features in Laravel 5.0.)

  1. Laravel 5.0 - Form Requests
  2. Laravel 5.0 - ValidatesWhenResolved
  3. Laravel 5.0 - Directory structure and namespace
  4. Laravel 5.0 - Route Caching
  5. Laravel 5.0 - Cloud File Drivers
  6. Laravel 5.0 - Method Injection
  7. Laravel 5.0 - Route Annotations (removed)
  8. Laravel 5.0 - Event Annotations (removed)
  9. Laravel 5.0 - Middleware (Filter-style)
  10. Laravel 5.0 - Environment Detection & Environment Variables
  11. Laravel 5.0 - Event Scheduling
  12. Laravel 5.0 - Commands & Handlers
  13. Upgrading from Laravel 4 to Laravel 5
  14. Bringing Whoops Back to Laravel 5
  15. Laravel 5.0 - Events & Handlers
  16. Laravel 5.0 - Generating Missing Events
  17. Laravel 5.0 - Custom Error Pages
  18. Laravel 5.0 - Eloquent Attribute Casting

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

Matt Stauffer headshot

Hi, I'm Matt Stauffer.

I'm partner & technical director at Tighten Co.

You can find me on Twitter at @stauffermatt


Like what you're reading?

I wrote an entire 450+ page book for O'Reilly: Laravel: Up and Running.

You can order the eBook or print book today.