Global site messages in Laravel 4

How to create a global site messaging system in Laravel 4

Almost every site I've ever made has needed some form of global error/success/information message system.

These messages generally come in two flavours:

  1. Generated on page load
  2. Flashed to the session and retrieved on the next page load (primarily for form submissions/redirects)

They are then passed into the view and displayed.

So, what's the best way to do this in Laravel 4? Using a message bag of course!

Message Bags

The MessageBag class is a great little container for all types of messages. It's basically a glorified key/value store, and sure, we could easily make our own, but why would you. One's already there for you, tried and tested, literally.

Its syntax is very simple:

$messageBag = new Illuminate\Support\MessageBag;

$messageBag->add('error', 'Error Message 1');
$messageBag->add('error', 'Error Message 2');

print_r($messageBag->get('error'));
// Array ( [0] => Error message 1 [1] => Error message 2 )

echo $messageBag->first('error');
// Error message 1

echo $messageBag->first('error', '<p>:message</p>');
// <p>Error message</p>

var_dump($messageBag->any());
// true

echo $messageBag->count();
// 2

Implementation

For the purposes of this tutorial, I'll assume you're using controllers, but this code can be used in routes as well with minimal tweaking; only the dependency injection won't be needed.

In your controller, let's inject the message bag in the constructor:

// YourController.php
use Illuminate\Support\Contracts\MessageProviderInterface;

class YourController extends Controller
{
    protected $messages;

    /**
     * __construct
     *
     * @param MessageProviderInterface $messages
     */
    public function __construct(MessageProviderInterface $messages)
    {
        $this->messages = $messages;
    }
}

Here, we're injecting our messages into our controller as a dependency, but notice how we don't explicitly use the MessageBag class, and use the MessageProviderInterface instead?.

This is so we can easily switch out our MessageBag class for a different one further down the line, and not have to edit this code. Passing interfaces as a dependency is generally a better idea, as it ensures you have a specific API to work with, without tying you down to a specific implementation. We'll need to bind the interface to a class using Laravel's IoC container. (Passing it in as a dependency and not instantiating it in the constructor also helps when testing, as we can pass in a mock when we do our testing.)

MessageProviderInterface only has one method in it, getMessageBag. This ensures we return our message bag complete with our messages, which we simply pass to our view:

return View::make('view')->with('messages', $this->messages->getMessageBag());

The reason we're passing the getMessageBag call to the view, and not just the $messages variable straight, (even though, more than likely, they are the same thing, as getMessageBag returns $this by default), is to future-proof the implementation slightly. We may end up using a class that doesn't extend the MessageBag class, but rather stores an instantiation of it as a variable, much like the Validator class.

Binding the Interface

We still need to use the IoC to bind the interface to our class so it'll know what to inject when our controller is resolved. To do this, place the following line in your start/global.php file.

App::bind(
    'Illuminate\Support\Contracts\MessageProviderInterface',
    'Illuminate\Support\MessageBag'
);

There has been some debate about where to place bind calls in your project. Service providers are generally the best idea, but are overkill for this tutorial.

That's it! Well, sort of…

We could technically stop here, but there are a few caveats with our implementation:

  • No way to flash messages and retrieve them on page load
  • If anything else has MessageProviderInterface as a dependency, and is created using the IoC, it'll use the MessageBag class

To combat the first issue, we're going to extend the MessageBag class with our own class.

FlashMessageBag

Create a new file called FlashMessageBag.php, and save it somewhere it can be autoloaded. Place the following code in it:

<?php

use Illuminate\Support\MessageBag;
use Illuminate\Session\Store;

class FlashMessageBag extends MessageBag {

    protected $session_key = 'flash_messages';
    protected $session;

    public function __construct(Store $session, $messages = array())
    {
        $this->session = $session;

        if ($session->has($this->session_key))
        {
            $messages = array_merge_recursive(
                $session->get($this->session_key),
                $messages
            );
        }

        parent::__construct($messages);
    }

    public function flash()
    {
        $this->session->flash($this->session_key, $this->messages);

        return $this;
    }
}

We're not adding much to the MessageBag class, but it's enough for what we need.

We're now passing in a session object into the class, type hinted with Store, which is what is returned when we use:

App::make('session.store');

We pass this in so we can technically use any session implementation we want, although realistically we'll only ever need to use the one specified in the site's config.

We're then checking if we have any flashed messages, and if we do, merge them with any messages passed in, and pass them to the parent's (MessageBag) constructor.

We've also added a new method: flash. This simply uses the session object we passed in to flash the messages, so they're available for the next request.

Updating the binding

Here's a bit I'm stil unsure on. Remember earlier, I said one of the caveats of our first binding was that any time MessageProviderInterface was injected, it'd use our binding? Well, we could create a new interface, and bind our implmentation to that. However, after searching through Laravel's codebase, MessageProviderInterface isn't actually injected anywhere else.

For this reason, and the fact we're not really adding much anyway, I don't think that creating a new Interface is necessary. Some people may disagree, and our code may break if Laravel does use the Interface as dependency in the future. So basically, it's up to you.

We need to update the bind call anyway, to reflect our new message bag implementation. Go to start/global.php and edit the bind call to this:

App::bind(
    'Illuminate\Support\Contracts\MessageProviderInterface',
    function()
    {
        return new FlashMessageBag(
            App::make('session.store')
        );
    }
);

Using FlashMessageBag

Now all the set up is done, we should use our code!

// In a controller method. E.g. POST login
$this->messages
    ->add('success', 'You have successfully logged in')
    ->flash();

return Redirect::to('dashboard');

// Another controller method. E.g. GET dashboard
return View::make('dashboard')
    ->with('messages', $this->messages->getMessageBag());

// In a view. E.g. views/dashboard
@if ($messages->has('success'))

    {{ $messages->first('success') }}

@endif

The messages get picked up automatically, so all you need to do is show them in the view.

And if you don't want to flash them, and just pass them straight to the view, don't call the flash method!

So there you have it, really simple, testable, global site messages.

Any issues or improvements, let me know in the comments!

Want to let me know what you think of Global site messages in Laravel 4? Why not leave a comment, follow me on Twitter , or !

Comments

comments powered by Disqus