Event Listeners for Statamic

Arlind Musliu Portrait
Arlind Musliu

March 9, 2022 · 5 min read · 1,934 views

Statamic Blogpost Image

Laravel's event-driven architecture is a powerful feature that allows developers to hook into the framework's core functionality and extend it in creative ways. Statamic, a flat-file CMS built on top of Laravel, leverages this architecture by offering a suite of events that can be used to customize and enhance your web application. These events cover all the essential aspects of Statamic, including Assets, Blueprints, Collections, Entries, Forms, Taxonomies, Users, and more. They are triggered by various actions such as creation, deletion, saving, uploading, and discovery of content within your Statamic site.

Understanding the Statamic Event System

Events in Statamic are dispatched in response to specific changes within the CMS. For instance, when content is added or updated, events like 'EntrySaved or EntrySaving' are triggered. These events are crucial for developers who wish to execute custom code at precise moments during the content management workflow. By listening to these events, you can perform tasks like updating external databases, integrating with third-party services, or modifying the behavior of the CMS itself.

Implement a Fuzzy Search Feature with Fuse.js

One practical application of Statamic events is to implement custom search functionality. While the default search capabilities of Statamic are robust, they may not support all use cases, such as searching text written in non-Latin alphabets like Cyrillic. To address this, you can integrate a fuzzy-search library like Fuse.js to enable approximate string matching. This can be particularly useful for large-scale projects or when you require more flexible search criteria. We'll delve into how to connect Fuse.js with Statamic and leverage fuzzy searching in a dedicated post. For large projects, we usually recommend using Algolia.

Create the event listener

To make it work, we need to create an event listener that will handle the event. The listener can be created with the following command and it will be found at app/Listeners.

php artisan make:listener SavePostsToJson

In our example, we want to create a JSON file that will keep the title, categories and tags of all posts. We need to create an event listener that will refresh the JSON data. Whenever we add a new post (save entry), the event EntrySaved will be called. Then, we can use the data of that entry to do some stuff that we might need. This is an overview of what you get after you create the listener:

<?php

use Statamic\Events\EntrySaved;

public function handle(EntrySaved $event)
{
    $event->entry;
}

Registering the listener

Before we get too excited with this, let's make sure that we have registered our listener, otherwise it won't work. Listeners are registered at app\Providers\EventServiceProvider. Navigate to this file and make sure you add the following lines of code and leave the rest of the code as you found it:

<?php

use App\Listeners\SavePostsToJson;
use Statamic\Events\EntrySaved;

class EventServiceProvider extends ServiceProvider
{

    protected $listen = [
        EntrySaved::class => [
            SavePostsToJson::class,
        ]
    ];
}

Query all posts

Let's get back to our event listener. We mentioned that we want to query all posts and that's why we need to include the Entry Facade. The saved data will be added to a new json file that will be located in the public directory.

<?php

namespace App\Listeners;

use Statamic\Facades\Entry;
use Statamic\Events\EntrySaved;

class SavePostsToJson
{
    public function handle(EntrySaved $event)
    {
        $data = Entry::query()
            ->where('collection', 'posts')
            ->get()
            ->transform(function ($post) {
                return [
                    'title' => $post->title,
                    'categories' => $post->categories,
                    'slug' => (string)$post->slug()
                ];
            })
            ->toArray();

        file_put_contents(public_path() . '/search/data.json', json_encode($data));
    }
}

That's it, we can now create a new post and the json data will be saved with all available posts.

Using the EntrySaved $event variable

Our current code has an issue. The listener is not triggered only when the user saves a post, but an entry. This means that whenever the user saves a page, product or whatever other entries we might have, it will trigger the event listener. To fix this, we will make use of the EntrySaved $event variable and its data.

To make it more challenging, let's say that we have a multisite in three different languages. We save the posts in different JSON files according to the locale that we have. Thus, we don't want to save all JSON files if we simply make a change on a particular language and not the rest. Let's see how we can implement this by further extending our example.

Firstly, we check if the saved entry is a post or not:

if ($event->entry->collection()->handle() == 'posts')

Then, we check to which language that post belongs:

if ($event->entry->site()->name() == 'en') {

To make it more understandable, without any code refactoring, our example will look like this:

<?php

namespace App\Listeners;

use Statamic\Facades\Entry;
use Statamic\Events\EntrySaved;

class SavePostsToJson
{
    public function handle(EntrySaved $event)
    {
        if ($event->entry->collection()->handle() == 'posts') {
            if ($event->entry->site()->name() == 'en') {
                $data = Entry::query()
                        ->where('collection', 'posts')
                        ->where('site', 'en')
                        ->where('published', true)
                        ->get()
                        ->transform(function ($post) {
                            return [
                                'title' => $post->title,
                                'categories' => $post->categories,
                                'slug' => (string)$post->slug()
                            ];
                          })
                          ->toArray();

                file_put_contents(public_path() . "/search/en/data.json", json_encode($data));
                
            } else {
                // some other language
            }
        }
    }
}

However, we will do some code refactoring to ensure that we comply with best practices. Our example has English and Albanian as alternative languages, and the default language is different.

<?php

namespace App\Listeners;

use Statamic\Facades\Entry;
use Statamic\Events\EntrySaved;

class SavePostsToJson
{
    public function handle(EntrySaved $event)
    {
        if ($event->entry->collection()->handle() == 'posts') {
            if ($event->entry->site()->name() == 'en') {
                $this->createEntry('en');
            } elseif ($event->entry->site()->name() == 'sq') {
                $this->createEntry('sq');
            } else {
                $this->createEntry('default');
            }
        }
    }

    protected function createEntry($lang) {

        $data = Entry::query()
            ->where('collection', 'posts')
            ->where('site', $lang)
            ->where('published', true)
            ->get()
            ->transform(function ($post) {
                return [
                    'title' => $post->title,
                    'categories' => $post->categories,
                    'slug' => (string)$post->slug()
                ];
            })
            ->toArray();

        file_put_contents(public_path() . "/search/{$lang}/data.json", json_encode($data));
    }
}

Debugging the data

If you want to see what other data the $event variable brings to the table, you can do so by including Laravel's Log service:

use Illuminate\Support\Facades\Log;

and adding the following code inside the handle function:

Log::debug(print_r($event->entry, true));

You must add it inside print_r() because the log expects a string and not an object. Then simply go to storage/logs/laravel.log and scroll to the end of the file to see what output you got.

Best Practices and Code Refactoring

As you build out your custom event listeners, it's important to follow best practices and maintain clean, modular code. Refactoring your listeners to handle different scenarios efficiently can improve the maintainability and readability of your codebase. Whether you're dealing with multiple languages or a variety of content types, a well-structured listener can greatly enhance the functionality of your Statamic-powered website.


Bring Your Ideas to Life 🚀

Kickstart Your Statamic Project with the #1 Statamic Agency

Are you planning a new Statamic project or thinking about migrating your WordPress site to Statamic? Learn more about our expertise as a renowned Statamic development agency.

Arlind Musliu Portrait
Arlind Musliu

Cofounder and CFO of Lucky Media

Technologies:

Statamic
Heading Pattern

Related Posts

Stay up to date

Be updated with all news, products and tips we share!