cult3

Using Events in Laravel 4

Mar 10, 2014

Table of contents:

  1. What are Events?
  2. Why use Events?
  3. Events in Laravel 4
  4. Firing an event
  5. Listening to events
  6. Queueing Events
  7. Listening to events using classes
  8. Firing Events in your application
  9. Leveraging Laravel’s core Events
  10. Registering an Event Subscriber using Service Providers
  11. Conclusion

Laravel 4 is a fantastic modern PHP framework for a number of reasons. One of my personal favourite things about Laravel 4 is how it is built around the architecture of events.

Events make it really easy for developers like us to hook on to or extend the underlying architecture of the framework in a clean and maintainable way. It also makes it really easy for us to create events for our own applications so that we create software that is easier to extend and maintain for others.

Using events in application development is not really a newbie topic, so I think a lot of developers who are just getting started miss out on this amazing opportunity to build better, more maintainable software. Thinking about building an event based architecture is as much of a shift in mindset as anything else.

In this tutorial I’m going to look at events, why you would want to use them and how you can leverage them to your advantage in Laravel 4.

What are Events?

In programming, an event is just an occurrence of a particular thing at a particular moment in time. For example, if you are creating a social application, an event you might be interested in could be whenever a new user signs up.

When a new user signs up in your application, you will probably have a list of things that you need to happen. For example, you will want to send the user an email, subscribe them to your newsletter or add them to a queue to suggest the next steps for using your application.

By defining the user create event, you can set all of these tasks in motion whenever a new user signs up for your application.

That is the basic theory of how events work. Some significant thing is triggered whilst another separate thing listens for the event to happen and then takes an action.

Why use Events?

So hopefully you understand the theory of how events work, why would you actually want to use them?

The beautiful thing about using an event-based architecture is, you maintain your separation of concerns. When an event is triggered it does not need to know anything about what will happen as a consequence of firing the event. This allows you to separate your events and your listeners into two distinct areas.

If you remember back to our social application analogy, imagine if we were dealing with registering a new user. The controller might look something like this:

public function store()
{
    $user = User::create(Input::all());
}

Next we need to send a welcome email to the new user:

public function store()
{
    $user = User::create();
    // send welcome email
}

We need to send a notification to our analytics software:

public function store()
{
    $user = User::create();
    // send welcome email
    // send analytics notification
}

We need to register the user for our newsletter:

public function store()
{
    $user = User::create();
    // send welcome email
    // send analytics notification
    // register for newsletter
}

We need to register the user for the ‘next steps’ course of using the application:

public function store()
{
    $user = User::create();
    // send welcome email
    // send analytics notification
    // register for newsletter
    // register 'next steps' course
}

And we need to put the user into a queue to suggest potential connection suggestions with our existing users:

public function store()
{
    $user = User::create();
    // send welcome email
    // send analytics notification
    // register for newsletter
    // register 'next steps' course
    // put the user in the queue
}

See how nasty this is getting? You might be able to get away with this when you first write this process, but imagine coming back to it at some point in the future when you need to add another thing to the process or you need to take something out. This controller has suddenly grown from creating a new user and transporting the flow of the application to conducting all of the individual processes that are required when a new user signs up for our application!

A better solution would be to use events:

public function store()
{
    $user = User::create();
    $response = Event::fire('user.create', array($user));
}

Now when a new user is created the user.create event is fired. This controller does not need to know what actually happens or what is in the list of things to do.

Now you can add or remove tasks to be completed when a new user is created by making individual modules that listen for that event to take place.

The Publish-Subscribe pattern is pretty beautiful when you realise how easy it can make separating these big gnarly processes into smaller, more manageable chunks.

So now that we understand the theory and the benefits of using events in our applications, let’s take a look at events in Laravel 4.

Events in Laravel 4

As with everything else in Laravel 4, creating and using events is pretty easy once you get your head around it.

The basic premise of an event structure is first you fire the event at the moment when the event occurs. You then create a listener to listen for that particular event. When the event is fired the listener will automatically kick in to gear and run the process that you have defined.

Firing an event

As we’ve seen above, firing an event is basically just a one liner you add where the event takes place:

Event::fire("user.create");

This will fire the user.create event in your application.

As I mentioned above, if you want to send parameters into the event you can simply include an array as the second argument:

Event::fire('user.create', array($user);

When a new user is created you need to get the user’s name and email address to send the welcome email. By passing the instance of $user we can use these details in the event listener.

If you want to fire two different events you can pass them as an array as the first argument of the fire method:

Event::fire(["user.create", "campaign.update"], [$user, $campaign]);

The fire method will return an array of responses that you can use to control or change what happens next in your application based upon what occurred in the event:

$response = Event::fire('user.create', array($user);

Listening to events

Listening to an event is as simple as defining what you want to listen for and then what you want to do when it happens. As with a lot of stuff in Laravel, this can be achieved using a Closure:

Event::listen("user.create", function ($user) {
    // Send welcome email
});

When you have multiple listeners to an event, sometimes you will want to stop the remaining listeners from running. To do that you can return false from the current listener:

Event::listen("user.create", function ($user) {
    // Send welcome email

    return false;
});

Sometimes you will want to listen for multiple events using a single listener. For example, you might want to listen for when a user is created or when a user is updated using the user.create or user.update events.

To do this you can register a wildcard listener:

Event::listen("user.*", function ($user) {
    // Send email
});

If you need to know what the current event is, you can use the firing method:

Event::listen("user.*", function ($user) {
    if (Event::firing() == "user.update") {
        // Send email
    }
});

Queueing Events

In all of the examples above I’ve been sending email notifications on a certain event. Sending emails in an application isn’t exactly the quickest thing, and so sending multiple emails is going to make your website hang whenever the event is fired. Instead you can register an event queue for dealing with this situation. This will queue an event to fire, but it won’t fire it immediately:

Event::queue("user.create", [$user]);

You can then register a queue flusher:

Event::flusher("user.create", function ($user) {
    //
});

And you can flush all queue events using the flush method:

Event::flush("sale.create");

Listening to events using classes

As I’ve mentioned in many of the previous tutorials in this series, I prefer to use classes to organise my code, rather than closures. I’ve got nothing against using closures in smaller projects or for testing a bit of code, but for bigger projects I think you really need to be using a class based structure.

To register a listener using a class instead of a closure, you pass the name of the class as the second parameter, rather than closure:

Event::listen("user.create", "UserHandler");

If you have multiple listeners for the same event you can prioritise them by passing a priority as the third parameter. The higher the number, the higher the priority:

Event::listen("user.create", "UserHandler", 10);

By default the Handler class will call the handle method when the event is fired:

class UserHandler
{
    public function handle(Model $user)
    {
        // Send welcome email
    }
}

If you want to use a different method, you can define it in when you register the listener:

Event::listen("user.create", "UserHandler@onCreate");

You can also subscribe to multiple events from a single class using a Subscriber:

class UserEventSubscriber
{
    /**
     * When a user is created
     */
    public function onCreate($event)
    {
        //
    }

    /**
     * When a user is updated
     */
    public function onUpdate($event)
    {
        //
    }

    /**
     * Register the listeners for the subscriber.
     *
     * @param Illuminate\Events\Dispatcher $events
     * @return array
     */
    public function subscribe($events)
    {
        $events->listen("user.create", "UserEventSubscriber@onCreate");

        $events->listen("user.update", "UserEventSubscriber@onUpdate");
    }
}

When using a Subscriber you need to define a subscribe method that is passed an instance of the Laravel Event Dispatcher.

You then need to register the Subscriber:

$subscriber = new UserEventSubscriber();

Event::subscribe($subscriber);

Firing Events in your application

As I’ve shown above, it’s pretty easy to fire an event using Laravel. However, if you are adding events to your application specific code, you don’t want to be using Laravel’s facades and you should probably be injecting the event dispatcher in through the constructor.

Both of these concerns can be mitigated because Laravel is clever enough to allow you to mock facades, but I still think it is good practice to always do things the right way.

For example, say you wanted to fire a custom even in your UserRepository.

First you need to inject an instance of the Laravel Event Dispatcher in through the constructor:

<?php namespace Cribbb\Repositories\User;

use Illuminate\Events\Dispatcher;
use Illuminate\Database\Eloquent\Model;
use Cribbb\Repositories\RepositoryInterface;

class EloquentUserRepository implements RepositoryInterface, UserRepository
{
    /**
     * @var Model
     */
    protected $user;

    /**
     * @var Dispatcher
     */
    protected $events;

    /**
     * Construct
     */
    public function __construct(Model $user, Dispatcher $events)
    {
        $this->user = $user;
        $this->events = $events;
    }
}

Next you can use the injected instance to fire your event:

/**
 * Create
 *
 * @param array $data
 * @return Model
 */
public function create(array $data)
{
    $user = $this->user->create($data);

    $this->events->fire('user.create', array($user));

    return $user;
}

Leveraging Laravel’s core Events

As I mentioned at the top of this tutorial, Laravel is very much built around an event based architecture. This means that the core framework is already firing events that you can hook on to so that you don’t have to register them yourself.

For example, in the code above I’m registering an event for when a new user is created. However, Eloquent already has an event that is fired when a new record is created.

So instead of registering our own event, we can simply listen for the specific Laravel event:

Event::listen("eloquent.created: user", "UserEventHandler@onCreate");

For a full list of the Laravel core events, take a look at this article from Jason Lewis.

Registering an Event Subscriber using Service Providers

Once you have an event firing in your application, now you will want to register your subscriber using a Service Provider. Again as I’ve mentioned many times in the past I prefer to organise my application using Service Providers so that the application is consistent throughout:

<?php namespace Cribbb\Events;

use Cribbb\Events\UserEventHandler;
use Illuminate\Support\ServiceProvider;

class EventServiceProvider extends ServiceProvider
{
    /**
     * Register
     */
    public function register()
    {
        $this->app->events->subscribe(new UserEventSubscriber());
    }
}

And finally add the following line to your app/config/app.php file:

"Cribbb\Events\EventServiceProvider";

Conclusion

The Publish-Subscribe pattern in modern web applications is a pretty nice way of thinking about how the different resources and services will talk to each other. By using events in your application it becomes much easier to write smaller, more concise modules of code that have a single responsibility. This also stops certain services or your Controllers from getting too bloated with responsibility as your application grows in complexity.

By using a Publish-Subscribe architecture we can build complex application using tiny modules of code that interact as independent nuggets of responsibility.

As I mentioned at the top of this post, using the Publish-Subscribe pattern is more of a shift in mindset than anything else. Once you start to think of your application as independent services it frees you of the stress of complexity.

This is a series of posts on building an entire Open Source application called Cribbb. All of the tutorials will be free to web, and all of the code is available on GitHub.

Philip Brown

@philipbrown

© Yellow Flag Ltd 2024.