March 9, 2022 · 5 min read · 2,033 views
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.
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.
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.
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;
}
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,
]
];
}
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.
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));
}
}
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.
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.
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.
Technologies:
Related Posts
Stay up to date
Be updated with all news, products and tips we share!