Pushing Updates with the ThruwayBundle for Symfony

In this post I walk through creating a basic Symfony project that uses the ThruwayBundle to publish update and new events to a Thruway WAMP (Web Application Messaging Protocol) router. This can be used to create a realtime system that monitors your traditional Symfony application.

This post touches just the surface of the ThruwayBundle. Hopefully I will continue this as a series. For now this is it though.

I assume that you are relatively familiar with Symfony and are able to create basic projects and connect to databases as well as do common Symfony tasks (cache:clear etc).

Let’s Go

Install the Symfony project:

$ symfony new SymfonyWithThruway
$ cd SymfonyWithThruway
$ composer require voryx/thruway:dev-master
$ composer require voryx/thruway-bundle:dev-master

You can just install just the bundle, but at the time of writing I wanted to use the latest dev master so I required that for voryx/thruway as well.

Add the ThruwayBundle and JMSSerializerBundle to AppKernel.php:

new JMS\SerializerBundle\JMSSerializerBundle(),
new Voryx\ThruwayBundle\VoryxThruwayBundle($this)

Add some configuration for the ThruwayBundle to your config.yml:

voryx_thruway:
    realm: 'realm1'
    url: 'ws://127.0.0.1:8081' #The url that the clients will use to connect to the router
    router:
        ip: '127.0.0.1'  # the ip that the router should start on
        port: '8080'  # public facing port.  If authentication is enabled, this port will be protected
        trusted_port: '8081' # Bypasses all authentication.  Use this for trusted clients.
#        authentication: 'in_memory'
    locations:
        bundles: ["AppBundle"]
#        files:
#            - "AcmeDemoBundleControllerDemoController"

CRUD

In this post, I am going to build an application that will highlight a common use-case for the ThruwayBundle.

When you are using the ThruwayBundle, you are most likely integrating some real-time aspects into an existing project. We will create a simple entity with a standard doctrine CRUD interface and then add hooks to publish updates to Thruway as entities are updated through the standard CRUD system.

Simple CRUD:

Create a simple entity:

php app/console doctrine:generate:entity --entity=AppBundle:UserStatus --format=annotation --fields="user_status:string(141) user_name:string(255)" --with-repository --no-interaction

This creates a UserStatus entity. Our marketing department wanted us to allow one more character than Twitter on user statuses so that we can say that we are better.

Make sure your database is configured.

If needed:

php app/console doctrine:database:create

Then:

php app/console doctrine:schema:create

Generate your regular CRUD:

php app/console doctrine:generate:crud --entity=AppBundle:UserStatus --with-write -n

Try the CRUD:

Start the web server:

php app/console server:start

Open a browser to: http://127.0.0.1:8000/userstatus/

You should be able to add and edit UserStatus entities through the forms-based CRUD that Symfony created for you.

Event Listening

In order for us to be notified on UserStatus updates, we will use a doctrine event listener.

First create UserStatusListener.php in src/AppBundle/EventListener/:

<?php

namespace AppBundle\EventListener;

use AppBundle\Entity\UserStatus;
use Doctrine\ORM\Event\LifecycleEventArgs;
use Voryx\ThruwayBundle\Client\ClientManager;

class UserStatusListener {
    /** @var ClientManager */
    private $thruway_client;

    function __construct($thruway_client)
    {
        $this->thruway_client = $thruway_client;
    }
    
    private function publishInfo(LifecycleEventArgs $args) {
        $entity = $args->getEntity();

        if ($entity instanceof UserStatus) {
            $this->thruway_client->publish("user_status.new", [$entity]);
        }
    }

    public function postPersist(LifecycleEventArgs $args)
    {
        $this->publishInfo($args);
    }

    public function postUpdate(LifecycleEventArgs $args) {
        $this->publishInfo($args);
    }
}

We now need to create an entry in services.yml for the listener service:

    user_status.listener:
         class: AppBundle\EventListener\UserStatusListener
         arguments:
             - "@thruway.client"
         tags:
             - { name: doctrine.event_listener, event: postPersist }
             - { name: doctrine.event_listener, event: postUpdate }

The tags will tell symfony to hook up the class as an event listener and send postPersist and postUpdate events to it. Notice that we are sending in the “@thruway.client” service so that we can publish events from inside the listener.

Clear the cache so the service container gets rebuilt:

php app/console cache:clear

Fire up the Thruway router so that we can have something to route messages:

php app/console thruway:router:start

Now if you add or update a user status, you should be able to see the messages getting published on the router console.

Seeing Events in the Browser

Lets make something that can use these events in the browser to update statuses real-time as they are changed by the “traditional” back end.

You can make an html file with:

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title></title>
</head>
<body>
<!-- you are asked by the autobahn people to please host production js on your own server -->
<!-- but this is ok for testing/dev -->
<script src="https://autobahn.s3.amazonaws.com/autobahnjs/latest/autobahn.min.jgz"></script>
<script>
    var connection = new autobahn.Connection({
        url: 'ws://127.0.0.1:8080/',
        realm: 'realm1'
    });

    connection.onopen = function (session) {

        // 1) subscribe to a topic
        function onevent(args) {
            console.log("Event:", args[0]);
        }
        session.subscribe('user_status.update', onevent);
        session.subscribe('user_status.new', onevent);
    };

    connection.open();
</script>
</body>
</html>

Now you should be able to open another browser window at http://127.0.0.1:8000/livestatus.html. Open the javascript console and update or add a status through the traditional CRUD stuff we made before. You should see the updates being logged there.

Building a page to display this information would be a whole post unto itself; so until someone writes that, it is an exercise for the reader.

3 responses to “Pushing Updates with the ThruwayBundle for Symfony

  1. Hi,
    (Sorry for my poor english).

    Thank you for your post.

    First question : If the Thruway server is not on the local server where is the browser. How is the config.yml and the url for the ws ?

    Second question : app/console start in dev environment and not in prod environment. How start thruway router ?

    Thank you.

  2. For production, you can start the symfony command with the flag: –env = prod

    As far as connecting to a remote router, you should be able to do something like this:

    voryx_thruway:
    realm: ‘myrealm’
    url: “ws://example.com:9090”
    trusted_url: “ws://example.com:9090”

Leave a Reply