Event Listeners for Statamic

Event Listeners for Statamic

Published: March 9, 2022

Updated: March 10, 2022

statamic

laravel

events

listeners

fuzzy searching

fuse.js

Statamic Event Listeners

Laravel events are helpful for modifying the core functionality of the app. The Statamic team has provided a bunch of very important events that are ready for various usages. You check them all out here. The provided events include all the core features of Statamic, such as Asset, Blueprint, Collection, Entry, Form, Taxonomy, User, etc. The events are dispatched after a certain change happens, such as Found, Deleted, Saved, Uploaded, Created. For some use cases there are different events, for example EntrySaved vs EntrySaving.

Use Case: Fuzzy Search

We tried to use the native search provided by Statamic CMS, but it wouldn't work for texts written in the Cyrillic alphabet. That's why we decided to implement Fuzzy Searching with the help of Fuse.js, a lightweight fuzzy-search library. In another post we will explain how to connect Fuse.js with Statamic and make use of Fuzzy Searching (finding strings that are approximately equal to a given pattern). 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 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 does that post belong:

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 does the $event variable bring 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.

Do you have a Statamic project in mind? Let's get in touch.

Stay up to date

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