Events

Basic usage

The Event class provides a simple observer implementation, allowing you to subscribe and listen for events in your application. For example, you may listen for when a user signs in and update their last login date.

Event::listen('auth.login', function ($user) {
    $user->last_login = new DateTime;
    $user->save();
});

This is event made available with the Event::fire method which is called as part of the user sign in logic, thereby making the logic extensible.

Event::fire('auth.login', [$user]);

Subscribing to events

The Event::listen method is primarily used to subscribe to events and can be done from anywhere within your application code. The first argument is the event name.

Event::listen('acme.blog.myevent', ...);

The second argument can be a closure that specifies what should happen when the event is fired. The closure can accept optional some arguments, provided by the firing event.

Event::listen('acme.blog.myevent', function ($arg1, $arg2) {
    // Do something
});

You may also pass a reference to any callable object or a dedicated event class and this will be used instead.

Event::listen('auth.login', [$this, 'LoginHandler']);

NOTE: The callable method can choose to specify all, some or none of the arguments. Either way the event will not throw any errors unless it specifies too many.

Where to register listeners

The most common place is the boot method of a Plugin registration file.

class Plugin extends PluginBase
{
    [...]

    public function boot()
    {
        Event::listen(...);
    }
}

Alternatively, plugins can supply a file named init.php in the plugin directory that you can use to place event registration logic. For example:

<?php

Event::listen(...);

Since none of these approaches is inherently "correct", choose an approach you feel comfortable with based on the size of your application.

Subscribe using priority

You may also specify a priority as the third argument when subscribing to events. Listeners with higher priority will be run first, while listeners that have the same priority will be run in order of subscription. Listeners with no priority provided are assigned the default priority of 0.

// Default Priority Event
Event::listen('auth.login', function () { ... });

// Runs after the default priority listener above (same priority, registered second)
Event::listen('auth.login', function () { ... }, 0);

// Runs first
Event::listen('auth.login', function () { ... }, 10);

// Runs second
Event::listen('auth.login', function () { ... }, 5);

NOTE: Wildcard listeners do not support priority.

Halting events

Event listeners can halt the progation of a global event to subsequent listeners through one of two methods:

  1. Returning false

    Event::listen('auth.login', function ($event) {
        // Handle the event
    
        return false;
    });
    
  2. Returning any non-null value when the event is fired with the halt parameter set to true:

    Event::listen('whos.the.captain', fn ($ship) => 'Kirk');
    Event::listen('whos.the.captain', fn ($ship) => 'I am', 10);
    Event::listen('whos.the.captain', fn ($ship) => null, 20);
    
    // Returns array of results ([null, 'I am', 'Kirk'])
    Event::fire('whos.the.captain', payload: ['USS Enterprise']);
    
    // Returns first non-null result ('I am')
    Event::fire('whos.the.captain', payload: ['USS Enterprise'], halt: true);
    

NOTE: Local events currently only support the second method, where the call to dispatch the event must set the halt parameter to true in order to allow for event halting by listeners.

Wildcard listeners

When registering an event listener, you may use asterisks to specify wildcard listeners. Wildcard listeners will receive the event name fired first, followed by the parameters passed through to the event as an array.

The following listener will handle all events that begin with foo..

Event::listen('foo.*', function ($event, $params) {
    // Handle the event...
});

$event provides the event name that was fired. $params is the event payload. This will be an array of arguments passed from the fired event.

Given the following event:

Event::fire('foo.event', function ($arg1, $arg2) {
   // ...
}

When using a wildcard listener, this would map to the following:

Event::listen('foo.*', function ($event, $params) {
    $arg1 = $params[0];
    $arg2 = $params[1];
}

Firing events

You may use the Event::fire method anywhere in your code to make the logic extensible. This means other developers, or even your own internal code, can "hook" to this point of code and inject specific logic. The first argument of should be the event name.

Event::fire('myevent')

It is always a good idea to prefix event names with your plugin namespace code, this will prevent collisions with other plugins.

Event::fire('acme.blog.myevent');

The second argument is an array of values that will be passed as arguments to the event listener subscribing to it.

Event::fire('acme.blog.myevent', [$arg1, $arg2]);

The third argument specifies whether the event should be a halting event, meaning it should halt if a "non null" value is returned. This argument is set to false by default.

Event::fire('acme.blog.myevent', [...], true);

If the event is halting, the first value returned with be captured.

// Single result, event halted
$result = Event::fire('acme.blog.myevent', [...], true);

Otherwise it returns a collection of all the responses from all the events in the form of an array.

// Multiple results, all events fired
$results = Event::fire('acme.blog.myevent', [...]);

Passing arguments by reference

When processing or filtering over a value passed to an event, you may prefix the variable with & to pass it by reference. This allows multiple listeners to manipulate the result and pass it to the next one.

Event::fire('cms.processContent', [&$content]);

When listening for the event, the argument also needs to be declared with the & symbol in the closure definition. In the example below, the $content variable will have "AB" appended to the result.

Event::listen('cms.processContent', function (&$content) {
    $content = $content . 'A';
});

Event::listen('cms.processContent', function (&$content) {
    $content = $content . 'B';
});

Queued events

Firing events can be deferred in conjunction with queues. Use the Event::queue method to "queue" the event for firing but not fire it immediately.

Event::queue('foo', [$user]);

You may use the Event::flush method to flush all queued events.

Event::flush('foo');

Using classes as listeners

In some cases, you may wish to use a class to handle an event rather than a Closure. Class event listeners will be resolved out of the Application IoC container, providing you with the full power of dependency injection on your listeners.

Subscribe to individual methods

The event class can be registered with the Event::listen method like any other, passing the class name as a string.

Event::listen('auth.login', 'LoginHandler');

By default, the handle method on the LoginHandler class will be called:

class LoginHandler
{
    public function handle($data)
    {
        // ...
    }
}

If you do not wish to use the default handle method, you may specify the method name that should be subscribed.

Event::listen('auth.login', 'LoginHandler@onLogin');

Subscribe to entire class

Event subscribers are classes that may subscribe to multiple events from within the class itself. Subscribers should define a subscribe method, which will be passed an event dispatcher instance.

class UserEventHandler
{
    /**
     * Handle user login events.
     */
    public function userLogin($event)
    {
        // ...
    }

    /**
     * Handle user logout events.
     */
    public function userLogout($event)
    {
        // ...
    }

    /**
     * Register the listeners for the subscriber.
     *
     * @param  Illuminate\Events\Dispatcher  $events
     * @return array
     */
    public function subscribe($events)
    {
        $events->listen('auth.login', 'UserEventHandler@userLogin');

        $events->listen('auth.logout', 'UserEventHandler@userLogout');
    }
}

Once the subscriber has been defined, it may be registered with the Event::subscribe method.

Event::subscribe(new UserEventHandler);

You may also use the Application IoC container to resolve your subscriber. To do so, simply pass the name of your subscriber to the subscribe method.

Event::subscribe('UserEventHandler');

Event emitter trait

Sometimes you want to bind events to a single instance of an object. You may use an alternative event system by implementing the Winter\Storm\Support\Traits\Emitter trait inside your class.

class UserManager
{
    use \Winter\Storm\Support\Traits\Emitter;
}

This trait provides a method to listen for events with bindEvent.

$manager = new UserManager;
$manager->bindEvent('user.beforeRegister', function ($user) {
    // Check if the $user is a spammer
});

NOTE: Wildcard listeners are currently not supported for this trait.

The fireEvent method is used to fire events.

$manager = new UserManager;
$manager->fireEvent('user.beforeRegister', [$user]);

These events will only occur on the local object as opposed to globally.

Copyright © 2024 Winter CMS