As part of my ongoing [NS3000](/tags/ns3000) project I ran into a number of situations, especially when creating and updating files, where my controllers started becoming large and unwieldy. This would make testing a laborious pain and I looked for alternatives. Thankfully, since I'm using the [Laravel framework](https://laravel.com/), I have access to their excellent [Events](https://laravel.com/docs/7.x/events) system and decided to use that to hive off excess operations and get them out of my controllers. Although this post deals with the Laravel implementation, please be aware that there are many other systems available for synchronous languages and frameworks that you can use. As long as you have a queue you can put elements on and something to listen to it and execute tasks you can do it. Setup of Events is pretty simple in Laravel, you create an Event, a Listener, register them together, and then call `event(new EventName(params))` in your code and it will work. Please note, the default behaviour for Laravel is to run them synchronously unless you have [set up a queue](https://laravel.com/docs/7.x/queues), but even if you don't do that they can still be excellent from a code maintainability perspective. ## Registering Events And Generating Scaffolding You can create the event class and listener class manually in your project and then register them, but Laravel also contains a handy, slightly backwards way to set everything up. In a default new Laravel project you should have a file called `app/Providers/EventServiceProvider.php`. In there set it up as follows: {{< highlight php >}} protected $listen = [ 'App\Events\YourDescriptiveEventName' => [ 'App\Listeners\YourDescriptiveListenerName', ], ]; {{< / highlight >}} Now in a terminal window in your project directory run: ``` php artisan event:generate ``` This will create the Event and Listener classes for you in the correct directories and give them the skeleton they need for you to get started. Keep in mind you can register multiple Listeners to a single Event, as well as use a Listener on multiple different Events. An example from my NS3000 project is as follows: {{< highlight php >}} protected $listen = [ 'App\Events\FileStored' => [ 'App\Listeners\SendFileToMetaWeb', 'App\Listeners\StoreInXapiand' ], 'App\Events\FileEdited' => [ 'App\Listeners\StoreInXapiand' ], ]; {{< / highlight >}} Here the `FileStored` class will activate two separate Listeners, while `StoreInXapiand` is used on both Events. ## Designing Events Events themselves can be pretty simple, you just want to capture all the important data in a way it can be serialised (this is especially if it's being queued up for asynchronous running). An example of the key parts of an Event is as follows: {{< highlight php >}} public $file; public $tags; public $metadata; public function __construct(File $file, Collection $tags, Collection $metadata) { $this->file = $file; $this->tags = $tags; $this->metadata = $metadata; } {{< / highlight >}} Here you can see we pass through three variables to the constructor, then store them against the class. ## Executing Events Running an Event is really simple thanks to Laravel's convention-over-configuration approach. You simply just put the following in your controller: {{< highlight php >}} event(new FileStored($file, $all_tags, $metadata)); {{< / highlight >}} ## Designing Listeners The Listener class just is mostly centered around it's `handle($event)` function. {{< highlight php >}} public function handle(FileStored $event) { $response = Http::post('http://metaweb:5000/file', [ 'id' => $event->file->id ]); if ($response->status() !== 200) { throw new Exception('Unable to send file to MetaWeb'); } } {{< / highlight >}} Here we're receive the event, and then make a POST request to our metadata extraction API and throw an exception if it failed. This API call wasn't required to be done during the controller and we don't want to be waiting on a response so it'd be a perfect candidate for queueing asynchronously. Furthermore, because we've put this into a self-contained function, we can now run tests against just this small bit of code., reducing the testing load on the controller. We can also do the same with loading the data into our full-text search database (Xapiand) which means even more fragments of code that can be tested in isolation and run without bulking out our controller.