Developed with love by KnpLabs Hire us for your project!
27

ratchet-bundle

by phillies2k

Symfony WebSocket bundle using the Ratchet WebSocket library

P2RatchetBundle

Version: 1.0.5

Installation

"require": {
    "p2/ratchet-bundle": "dev-master"
}

Configuration

p2_ratchet:
    provider: ~             # The client provider to use, null for default
    address: 0.0.0.0        # The address to receive sockets on (0.0.0.0 means receive from any)
    port: 8080              # The port the socket server will listen on

Usage

  • Implement the ClientInterface in your applications user model or document.
  • Implement the ClientProviderInterface in your applications user provider or managing repository.
  • Set the provider setting to the service id of your applications client provider implementation or leave blank for the default anonymous provider.
  • Implement the ApplicationInterface to listen on your own socket events (Getting started).
  • Use the {{ websocket_client(token, debug) }} macro within your templates to enable the frontend websocket client.
  • Write your client side event handler scripts. See the Javascript API section for more detail.
  • Open a terminal and start the server app/console socket:server:start. By default it will accept connection from *:8080 (see Command Line Tool)

Getting started

The ApplicationInterface acts only as an alias for symfony`s EventSubscriberInterface. Its used to detect websocket event subscribers explicitly.

Write your application as you would write a common event subscriber. The event handler methods will receive exactly one argument: a ConnectionEvent instance, containing information about the socket connection and the payload (see ConnectionInterface and Payload for more details).

# src/Acme/Bundle/ChatBundle/WebSocket/Application.php
<?php

namespace Acme\Bundle\ChatBundle\WebSocket;

use P2\Bundle\RatchetBundle\WebSocket\Server\ApplicationInterface;

class Application implements ApplicationInterface
{
    public static function getSubscribedEvents()
    {
        return array(
            'acme.websocket.some.event' => 'onSomeEvent'
            // ...
        );
    }

    // put your event handler code here ...
}

Service DI Configuration

Create a service definition for your websocket application. Tag your service definition with kernel.event_subscriber and p2_ratchet.application to register the application to the server.

The service definition may look like this:
```yaml

src/Acme/Bundle/ChatBundle/Resources/config/services.yml

services:

# websocket chat application
websocket_chat:
    class: Acme\Bundle\ChatBundle\WebSocket\ChatApplication
    tags:
        - { name: kernel.event_subscriber }
        - { name: p2_ratchet.application }


### Command Line Tool

```bash
php app/console socket:server:start [port] [address]

Events

Event Description
SOCKET_OPEN Fired when the server received a new connection.
SOCKET_CLOSE Fired when the socket connection was closed.
SOCKET_ERROR Fired when an error occurred during transmission.
SOCKET_MESSAGE Fired when a message was send through a connection.

WebSocket Events

Client:
Event Payload Description
socket.auth.request { token } This event is dispatched by the javascript client directly after the socket connection was opened. Its attempt is to send the value of p2_ratchet_access_token to the server to identify the websocket client within your application.
Server:
Event Payload Description
socket.auth.success { client } Fired on a successful authentication request. The payload contains the public user data returned by ClientInterface::jsonSerialize()
socket.auth.failure { errors } Fired when an error occurred during the authentication process. The payload contains the errors returned.

Hook-in Points

The bundle allows you to hook into the react event loop to add your own periodic timers. All you have to do is to create a class implementing PeriodicTimerInterface and to tag it as "p2_ratchet.periodic_timer".
Then the timers will be added to the loop on server startup.

Example:
# src/Acme/Bundle/ChatBundle/WebSocket/Loop/CustomTimer.php
<?php

namespace Acme\Bundle\ChatBundle\WebSocket\Loop;

use P2\Bundle\RatchetBundle\WebSocket\Server\Loop\PeriodicTimerInterface;

class CustomTimer implements PeriodicTimerInterface
{
    /**
     * Returns the interval for this timer
     *
     * @return int
     */
    public function getInterval()
    {
        return 60; // execute this timer once per minute
    }

    /**
     * Returns the callback.
     *
     * @return callable
     */
    public function getCallback()
    {
        return function() {
            // do something
        };
    }

    /**
     * Returns a unique name for this timer.
     *
     * @return string
     */
    public function getName()
    {
        return 'custom_timer';
    }
}
Service
    # my custom timer
    acme_chat.websocket.loop.custom_timer:
        class: %acme_chat.websocket.loop.custom_timer%
        tags:
            - { name: p2_ratchet.periodic_timer }

Javascript API

The api represents just a simple wrapper for the native javascript WebSocket to ease developers life. It basically implements the basic communication logic with the socket server.


// create the websocket
var socket = new Ratchet('ws://localhost:8080');

// implement your custom event handlers
socket.on('my.custom.event', function(data) {
    // ...
});

// emit an event
socket.emit('some.event', {
    // event data...
});

Simple chat application example

The application code:
```php

src/Acme/Bundle/ChatBundle/WebSocket/ChatApplication.php

<?php

namespace Acme\Bundle\ChatBundle\WebSocket;

use P2\Bundle\RatchetBundle\WebSocket\ConnectionEvent;
use P2\Bundle\RatchetBundle\WebSocket\Payload;
use P2\Bundle\RatchetBundle\WebSocket\Server\ApplicationInterface;

class ChatApplication implements ApplicationInterface
{
public static function getSubscribedEvents()
{
return array(
'chat.send' => 'onSendMessage'
);
}

public function onSendMessage(MessageEvent $event)
{
    $client = $event->getConnection()->getClient()->jsonSerialize();
    $message = $event->getPayload()->getData();

    $event->getConnection()->broadcast(
        new EventPayload(
            'chat.message',
            array(
                'client' => $client,
                'message' => $message
            )
        )
    );

    $event->getConnection()->emit(
        new EventPayload(
            'chat.message.sent',
            array(
                'client' => $client,
                'message' => $message
            )
        )
    );
}

}


The respective twig template may look like this:

```html
# src/Acme/Bundle/ChatBundle/Resources/views/chat.html.twig
{% extends '::base.html.twig' %}

{% import 'P2RatchetBundle::client.html.twig' as p2_ratchet %}

{% block stylesheets %}

    <style type="text/css">
        #chat { width: 760px; margin: 0 auto; }
        #chat_frame { overflow: hidden; position: relative; height: 320px; line-height: 16px; font-size: 12px; font-family: monospace; border: 1px solid #a7a7a7; margin-bottom: 10px; border-radius: 10px; }
        #chat_buffer { line-height: 16px; font-size: 12px; font-family: monospace; min-height: 300px; position: absolute; bottom: 0; left: 0; padding: 10px 20px; width: 720px; }
        #chat_buffer > p { margin: 0; padding: 0; font-size: inherit; line-height: inherit; color: dimgray; }
        #chat_buffer > p > em { font-weight: bold; color: deepskyblue; }
        #chat_buffer > p > span { color: dimgray; }
        #send_message { background: #f5f5f5; border: 1px solid darkgray; border-radius: 10px; padding: 20px; }
        #message { padding: 8px 5px; border: 1px solid darkgray; font-size: 14px; line-height: 16px; width: 100%; box-sizing: border-box; }
    </style>

{% endblock %}

{% block body %}

    <section id="chat">
        <div id="chat_frame">
            <div id="chat_buffer"></div>
        </div>

        <form id="send_message" method="post" action="">
            <input type="text" id="message" name="message" placeholder="...">
        </form>

    </section>

{% endblock %}

{% block javascripts %}

    <script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>

    {{ p2_ratchet.websocket_client(app.user.accessToken|default(''), app.debug) }}

    <script type="text/javascript">
        $(function() {

            function appendChatMessage(response) {
                $('#chat_buffer').append(
                        $('<p>[<em>' + response.client.username + '</em>]: <span>' + response.message + '</span></p>')
                );
            }

            var server = new Ratchet('ws://localhost:8080');

            // bind listeners
            server.on('chat.message.sent', appendChatMessage);
            server.on('chat.message', appendChatMessage);

            $('#send_message').submit(function(e) {
                e.preventDefault();

                var message = $('#message');
                var value = message.val();

                if (value.length) {
                    server.emit('chat.send', value);
                    message.val("");
                }

                return false;
            });
        });
    </script>

{% endblock %}


Copyright (c) 2013 Philipp Boes

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
  • Fix client script
    By phillies2k, 3 years ago
  • Fix client script
    By phillies2k, 3 years ago
  • Fix ratchet version to v0.2.8
    By phillies2k, 3 years ago
  • Remove deprecated twig extension service definition and fix readme
    By phillies2k, 3 years ago
  • Update readme and fix namespace errors
    By phillies2k, 3 years ago
  • Update readme and fix namespace errors
    By phillies2k, 3 years ago
  • Fix readme bug
    By phillies2k, 3 years ago
  • Add possibility to hook into react event loop
    By phillies2k, 3 years ago
  • Remove twig extension and provide the js client as macro (smarter way)
    By phillies2k, 3 years ago
  • Several code fixes
    By phillies2k, 3 years ago
  • Slightly refactor websocket.js to not expose internal functionality anymore
    By phillies2k, 3 years ago
  • Merge pull request #3 from phiamo/extensions
    By phillies2k, 3 years ago
  • Added variable
    By phiamo, 3 years ago
  • prepared 1.0.5
    By phillies2k, 3 years ago
  • prepared 1.0.5
    By phillies2k, 3 years ago
  • prepared 1.0.5
    By phillies2k, 3 years ago
  • prepared 1.0.5
    By phillies2k, 3 years ago
  • prepared 1.0.5
    By phillies2k, 3 years ago
  • prepared 1.0.5
    By phillies2k, 3 years ago
  • prepared 1.0.5
    By phillies2k, 3 years ago
  • prepared 1.0.5
    By phillies2k, 3 years ago
  • prepared 1.0.5
    By phillies2k, 3 years ago
  • prepared 1.0.5
    By phillies2k, 3 years ago
  • prepared 1.0.5
    By phillies2k, 3 years ago
  • prepared 1.0.5
    By phillies2k, 3 years ago
  • prepared 1.0.5
    By phillies2k, 3 years ago
  • prepared 1.0.5
    By phillies2k, 3 years ago
  • prepared 1.0.5
    By phillies2k, 3 years ago
  • fixed readme
    By phillies2k, 3 years ago
  • removed useless statement
    By phillies2k, 3 years ago