cult3

Setting up Pusher in a Laravel Application

Nov 16, 2015

Table of contents:

  1. Setting the scene
  2. Add the Pusher SDK
  3. Creating the Client
  4. Dealing with Storage
  5. Conclusion

The standard for modern software has never been higher. Users have comes to expect interactive and immersive experiences across desktop, web, and mobile.

If you do not provide this kind of experience, you will fall behind your competitors.

Fortunately, there has never been a better time to build these kinds of experiences. We now have a number of tools and services to make it a lot easier.

One such tool is Pusher. Pusher is a service that allows you to very easily make your software “real-time”.

Instead of setting up the infrastructure to deliver real-time data to your applications, you can drop Pusher into your project and get going in not very much time at all.

Watching your application update in real-time is like magic and will add a huge amount of credibility in the eyes of your customers.

In today’s tutorial we’ll be looking at integrating Pusher into a Laravel application.

Setting the scene

Before I jump into implementing this functionality, first I will explain the situation that I’m building for.

The great thing about Pusher is, it’s a generic service for delivering web socket data, and so it will work in any type of application.

However for this example, it’s important to know what situation I’m using it for.

Imagine we’re building a B2B Software as a Service web application. The application represents companies as “Accounts”.

Whenever something happens in an Account, we need to push the new data to all of the active Users of that Account.

So with that out of the way, let’s take a look at adding Pusher to a Laravel application. I’m going to assume that you’ve already set up a Pusher account and you’re ready to go.

Add the Pusher SDK

The first thing to do is to add the Pusher SDK to the application:

$ composer require pusher/pusher-php-server

We’re going to need to send API requests to Pusher and so using the SDK will give us a jump start. Normally I like to avoid using an SDK and just make my own HTTP requests using Guzzle.

However, if you’re pushed for time (aren’t we all?) wrapping the SDK is a decent second choice. We can always replace it in the future.

You can also add your Pusher credentials to the services.php configuration file.

Creating the Client

The first thing we need to do is to create the Client that will be responsible for making the requests to Pusher. As I mentioned above, in this tutorial we’re going to be using the Pusher SDK. But we could also just make the HTTP requests ourselves using a package like Guzzle.

So as to not tie ourselves to one implementation, first we can define a Client interface:

interface Client
{
    /**
     * Authenticate a private channel
     *
     * @param string $channel
     * @param string $socket
     * @param string $data
     * @return string
     */
    public function authenticate($channel, $socket, $data = false);

    /**
     * Send a message to a channel
     *
     * @param string $channel
     * @param string $event
     * @param array $data
     * @return void
     */
    public function send($channel, $event, array $data);
}

This interface requires two methods, one for authentication and one for sending messages.

Next we can create the Pusher SDK Client implementation. First I will inject an instance of the PusherSDK:

use Pusher as PusherSDK;

class PusherClient implements Client
{
    /**
     * @var PusherSDK
     */
    private $pusher;

    /**
     * @param PusherSDK $pusher
     * @return void
     */
    public function __construct(PusherSDK $pusher)
    {
        $this->pusher = $pusher;
    }
}

Next I will implement the two methods defined by the Client contract:

/**
 * Authenticate a private channel
 *
 * @param string $channel
 * @param string $socket
 * @param string $data
 * @return string
 */
public function authenticate($channel, $socket, $data = false)
{
    return $this->pusher->socket_auth($channel, $socket, $data);
}

/**
 * Send a message to a channel
 *
 * @param string $channel
 * @param string $event
 * @param array $data
 * @return void
 */
public function send($channel, $event, array $data)
{
    return $this->pusher->trigger($channel, $event, $data);
}

Hopefully you can see that it would be really easy to replace this class with a Guzzle implementation.

Dealing with Storage

Next we need to deal with Storage. When a user authenticates with our application, we will receive a request from Pusher. This user can be stored in a list of active user for the account.

Whenever an event occurs in the application that requires us to send the new data to all of the users, we can query this storage.

In production we can use Redis to store this data and during testing we can use a simple array implementation to store the data.

So once again, as we need two implementations, we first need an interface:

interface Storage
{
    /**
     * Get an item from the storage
     *
     * @param string $key
     * @return array
     */
    public function get($key);

    /**
     * Set an item in the storage
     *
     * @param string $key
     * @param array $value
     * @return void
     */
    public function set($key, $value);

    /**
     * Remove an item from the storage
     *
     * @param string $key
     * @return void
     */
    public function remove($key);
}

This interface is fairly simple as we only need get(), set() and remove() methods.

The first implementation I will create will be the ArrayStorage we can use for testing:

class ArrayStorage implements Storage
{
    /**
     * @var array
     */
    private $data = [];

    /**
     * Get an item from the Storage
     *
     * @param string $key
     * @return array
     */
    public function get($key)
    {
        return array_get($this->data, $key, []);
    }

    /**
     * Set an item in the Storage
     *
     * @param string $key
     * @param array $value
     * @return void
     */
    public function set($key, $value)
    {
        $this->data[$key] = $value;
    }

    /**
     * Remove an item from the Storage
     *
     * @param string $key
     * @return void
     */
    public function remove($key)
    {
        unset($this->data[$key]);
    }
}

As you can see, we are simply storing the data in an array on the class.

The RedisStorage class looks like this:

use Illuminate\Redis\Database;

class RedisStorage implements Storage
{
    /**
     * @var Database
     */
    private $database;

    /**
     * @param Database $database
     * @return void
     */
    public function __construct(Database $database)
    {
        $this->database = $database;
    }
}

First we inject an instance of Laravel’s Redis Database.

Next we define a private method for generating a key to store the data:

/**
 * Generate a key
 *
 * @param string $key
 * @return string
 */
private function key($key)
{
    return sprintf('pusher.webhooks.account.%s', $key);
}

Finally we can implement the methods of the Storage contract:

/**
 * Get an item from the Storage*
 *
 * @param string $key
 * @return array
 */
public function get($key)
{
    $value = $this->database->get($this->key($key));

    $value = json_decode($value);

    return is_array($value) ? $value : [];
}

/**
 * Set an item in the Storage
 *
 * @param string $key
 * @param array $value
 * @return void
 */
public function set($key, $value)
{
    $this->database->set($this->key($key), json_encode($value));
}

/**
 * Remove an item from the Storage
 *
 * @param string $key
 * @return void
 */
public function remove($key)
{
    $this->database->del($this->key($key));
}

With these two Storage implementations we’ve made it really easy to test the remaining functionality we will need to build.

Conclusion

In today’s tutorial we have set the foundation for adding Pusher to a Laravel application. We’ve covered two important concepts when working with external “services”.

Firstly, don’t tie yourself to a single implementation. We don’t want to reference the Pusher SDK anywhere in our code.

Think of the external SDKs as foreign bodies that you want to completely control. Letting them slip into the bloodstream of the application is like a cancer.

We’ve made it very easy to replace the Client in the future. I’m not a big fan of using SDKs so this is something I always inevitably do.

Secondly we need to store data in Redis. During our tests we could hit an actual Redis server, and I’m not totally against this, but there is not need for that overhead. Ideally we want to be able to run the tests in isolation.

By also defining a simple ArrayStorage implementation, we can simply use that implementation during testing.

And if we ever wanted to change from Redis to another storage type, we only have to create a new implementation.

Next week we will be looking at authenticating with Pusher.

Philip Brown

@philipbrown

© Yellow Flag Ltd 2024.