Use a trait in Laravel to make it easier to toast

Using a trait for adding toasts (notifications) to a Laravel/Inertia app

Published March 31st, 2022

I love me some toast, and no, no one is having a stroke. Actual toasts! Sadly, not the bread sort or champagne, but those little notifications that something has happened. Success… failure… warning… toast it all! Oh, and maybe you call them 'toasts' or 'notifications' or whatever - but in this instance, I'm talking about the little message that appears when something happens, and is typically time-sensitive and temporarily shown.

In a current Laravel app I’m working on (or is it apps, as technically there’s 3 different front ends for it), I have the need for toasts – controllers create them, middleware create them, and Inertia needs to know about them to display them.

The overall concept is pretty simple: when a toast is needed, pop it in the session – and when handling the request, and a toast exists, return it in the response.

So what do I need to know for my toasts? For a good toast you need:

  • a title

  • optionally a message

  • a type, such as “success” or “warning”

  • a boolean flag for persistence

  • champagne

  • bread

To create a toast, it’s simple then: just add a title, maybe a message, and a type to the session. And then update the HandleInertiaRequests.php file to take that from the session and include in its response.

To create a toast:

Request::session()->flash('toast', [
    'title'   => 'Saved',
    'message' => 'Your awesome model was successfully saved.',
    'type'    => 'success'
]);

And then if one exists, Inertia will fling it back in the response in the HandleInertiaRequests middleware.

public function share(\Illuminate\Http\Request $request)
{
	// don't forget all of your awesome Inertia response handling too

    return array_merge(parent::share($request), [
        'toast' => $request->session()->get('toast', null)
    ]);
}

But if I have a dozen controllers, each which need to add toasts for different reasons – creating or updating a model, destroying a model – I don’t want to have to remember what I called all of these properties – is it “title” or is it “name”? It would be repeating the code again and again – and requiring more intimate knowledge of exactly what structure is required.

While one can copy and paste code, needing to remember the exact data structure each time is just a nuisance that can easily be worked around.

To you, dear reader, I toast you this: let’s make all of our lives easier by handling this behaviour with a trait!

Let’s toast this with a trait

A trait in PHP allows you quite simply reuse code. Each function in your defined trait gets included in your using class, bringing with it whatever functionality you define. Neat, hey?

A simple solution here is to create an AddsToast trait to my app – so within the App\Traits folder, create AddsToast.php.

And within that trait, add a addToast function that accepts the different properties needed, and adds them to the session.

trait AddsToast
{
    protected function addToast(string $title, string $message, string $type, bool $alwaysShow)
    {
        $toast = [
            'title'      => $title,
            'message'    => $message,
            'type'       => $type,
            'alwaysShow' => $alwaysShow
        ];

        Request::session()->flash('toast', $toast);
    }
}

This is handy because code hinting in my IDE will be able to tell me what those properties are – so if I need a reminder of the property order, just check the hint - whether it is 'title' or 'name' or 'heading' - ultimately it doesn't matter when in use - the first property will always be the header title name thing, and the trait will take care of the right toast structure in the session.

To use the trait, within my controller (or middleware or whatever other piece of code – service, job, you name it), just say you want to use it:

class ChampagneController extends Controller
{
    use AddsToast;

    /* ... */
}

This will now suck the AddsToast in to the controller, and give me access to the addToast function.

class ChampagneController extends Controller
{
    use AddsToast;
    
    /* ... */

    public function update(Champagne $champagne, UpdateChampagneRequest $request)
    {
        // update the champagne
        $champagne->update($request->validated());

        // add a toast
        $this->addToast('Updated', 'Delicious champagne has been updated', 'success', false);

        // return back
        return Redirect::back();
    }
}

A huge advantage here is that the structure of the toast within the session is now managed in one central spot. I don’t need to access the session every single time I need to store a toast – I just call the function and it handles that for me. The function call accepts the properties, just as individual variables, and is solely responsible for adding them to the session.

If I wanted to rename the $title variable to be $name I can easily do this in one place without impacting the behaviour of other parts of my code.

A toast will always need a title, and most often (assuming things don’t go pear shaped), a “success” type toast. So how’s this for an idea: why not require a title, but make the message and type be optional, and with a default set type of "success" that will automatically hide (assuming my front end knows how to do that). 

trait AddsToast
{
    protected function addToast(string $title, string $message = null, string $type = 'success', bool $alwaysShow = false)
    {
        $toast = [
            'title'      => $title,
            'message'    => $message,
            'type'       => $type,
            'alwaysShow' => $alwaysShow
        ];

        Request::session()->flash('toast', $toast);
    }
}

Now, if I need a simple success message, with just a title, I could quite simply call:

$this->addToast('Saved');

This will give me a success toast, with the title of "Saved", that will automatically disappear after a few seconds. Readability for the win!

This helps keep each addToast call be cleaner especially when the optional properties don’t need to be set or changed.

But wait, it gets better... in the HandleInertiaRequests middleware, if you have something in your error bag, you could have a warning automatically returned - this is great for validation - set the logic once, and the entire system just knows what to do.

public function share(\Illuminate\Http\Request $request)
{
    $toast = null;
    if ($request->session()->has('errors')) {
        $toast = [
            'title'      => 'Check your details, and try again.',
            'message'    => null,
            'type'       => 'warning',
            'alwaysShow' => false
        ];
    } else {
        $toast = $request->session()->get('toast', null);
    }

	// don't forget all of your awesome Inertia response handling too

    return array_merge(parent::share($request), [
        'toast' => $toast
    ]);
}

The one downside is that there can only be one toast per response – which is probably a good thing because if your app needs to make multiple toasts from a single response, perhaps you need to rethink your intended action and response - bombarding with too many notifications at once could get overwhelming. One request, up to one toast – makes clean logical sense.

Using a trait makes it so easy to have different bits of code have the capability to add a toast to a response with a simple function code.

If I need to change the way the response gets handled – maybe a property needs renaming or shuffling – the storage logic is in a single reusable and highly readable piece of code. It could even be extended to allow for multiple toasts too because – you guessed it – a single reusable chunk of code handles the actual logic: the other parts that need it get to then take effortless advantage of it. Too easy! Now I'll toast to that!

You may be interested in...