📜 ⬆️ ⬇️

Creating a Live Account widget using PHP Web Sockets

Implementing web sockets allows a web application to process data in real time without resorting to hacks, such as long-polling.
One example of application is the display of the results of a sports match. Even now, many sites that show this data use Flash applications, since Action Script allows you to communicate with the server through a socket connection. However, vev sockets allow you to do the same using only HTML and JavaScript. We will try to do this in this guide using a php server.
image

Installation and Setup


We will use the Ratchet library, which allows PHP to use web sockets.
Create the following composer.json, which establishes both this dependency and autoload for the code we write below:
{ "require": { "cboden/Ratchet": "0.2.*" }, "autoload": { "psr-0": { "LiveScores": "src" } } } 

Now create the following directory structure:
 [root] bin src LiveScores public assets css vendor js vendor vendor 

You may want to take everything from the repository , which contains a number of necessary css, js and images, as well as all the code from this guide. If you want to write everything from scratch, in parallel with the manual, then copy only public / assets / * / vendor .
Naturally, do not forget to run php composer.phar update . If you do not have composer installed, install it by running curl -sS getcomposer.org/installer | php .
We will start by creating a class that will accept connections and send messages. Later, we will use it to update data on running games. This is a class basis to show how a message broker works:
 // src/LiveScores/Scores.php <?php namespace LiveScores; use Ratchet\MessageComponentInterface; use Ratchet\ConnectionInterface; class Scores implements MessageComponentInterface { private $clients; public function __construct() { $this->clients = new \SplObjectStorage; } public function onOpen(ConnectionInterface $conn) { $this->clients->attach($conn); } public function onMessage(ConnectionInterface $from, $msg) { foreach ($this->clients as $client) { if ($from !== $client) { // The sender is not the receiver, send to each client connected $client->send($msg); } } } public function onClose(ConnectionInterface $conn) { $this->clients->detach($conn); } public function onError(ConnectionInterface $conn, \Exception $e) { $conn->close(); } } 

It is important to note:

The next step is to create a daemon that will create an instance of our class, listen for incoming connections. Create a file:
 // bin/server.php <?php use Ratchet\Server\IoServer; use Ratchet\WebSocket\WsServer; use LiveScores\Scores; require dirname(__DIR__) . '/vendor/autoload.php'; $server = IoServer::factory( new WsServer( new Scores() ) , 8080 ); $server->run(); 

All this needs explanation. WsServer is an implementation of a more general class IoServer, which transmits data through a web socket. We will listen to port 8080. You can choose any port, the main thing is to check that it is not blocked by the wallmauer.

Maintaining state


We will track the current state of the game, no need to save data. Every time there is a change in the account in the game, we will update the data on the server and send them to all connected clients.
First, we must generate fixtures (for example, a list of games). For simplicity, we will do this randomly, and leave the set of fixtures active for the duration of the daemon’s execution time:
 // src/LiveScores/Fixtures.php <?php namespace LiveScores; class Fixtures { public static function random() { $teams = array("Arsenal", "Aston Villa", "Cardiff", "Chelsea", "Crystal Palace", "Everton", "Fulham", "Hull", "Liverpool", "Man City", "Man Utd", "Newcastle", "Norwich", "Southampton", "Stoke", "Sunderland", "Swansea", "Tottenham", "West Brom", "West Ham"); shuffle($teams); for ($i = 0; $i <= count($teams); $i++) { $id = uniqid(); $games[$id] = array( 'id' => $id, 'home' => array( 'team' => array_pop($teams), 'score' => 0, ), 'away' => array( 'team' => array_pop($teams), 'score' => 0, ), ); } return $games; } } 

Please note that we assign each game a unique id, which we will use further to indicate in which game the event occurred. Let's return to our Score class:
 // src/LiveScores/Scores.php public function __construct() { // Create a collection of clients $this->clients = new \SplObjectStorage; $this->games = Fixtures::random(); } 

Since the client can connect our widget at any time of the game, it is important to us that he would receive a current account. One way to do this is to respond to a new request by sending the current state of the game, then display a list of games and their score on the client.
Here is an implementation of the onOpen method that does this:
 // src/LiveScores/Scores.php public function onOpen(ConnectionInterface $conn) { // Store the new connection to send messages to later $this->clients->attach($conn); // New connection, send it the current set of matches $conn->send(json_encode(array('type' => 'init', 'games' => $this->games))); echo "New connection! ({$conn->resourceId})\n"; } 

I note that the message we send as a JSON object, where the type of event is a property. You don't have to use JSON, you can use any other format, but so that we can send structured data.

HTML


Since we send data through a web socket, and we will display it using JavaScript, the page’s HTML code is very simple:
 <div id="scoreboard"> <table> </table> </div> 

The row in the table with the results will look like this:
 <tr data-game-id="SOME-IDENTIFIER"> <td class="team home"> <h3>HOME TEAM NAME</h3> </td> <td class="score home"> <div id="counter-0-home"></div> </td> <td class="divider"> <p>:</p> </td> <td class="score away"> <div id="counter-0-away"></div> </td> <td class="team away"> <h3>AWAY TEAM NAME</h3> </td> </tr> 

The id of the Counter- * - * elements will be used later in the JS-plugin.

Javascript


Let's get started with javascript. The first thing to do is open a socket:
 var conn = new WebSocket('ws://localhost:8080'); 

You may have to replace the host address or port, depending on the specified server / daemon settings.
Next, you need an event handler that will be processed when a message is received:
 conn.onmessage = function(e) { 

The message is in the data property of the e event object. Since we send messages in JSON, we must first parse it:
 var message = $.parseJSON(e.data); 

Now we can check the message type and call the appropriate function:
 switch (message.type) { case 'init': setupScoreboard(message); break; case 'goal': goal(message); break; } 

The setupScoreboard function is quite simple:
 function setupScoreboard(message) { // Create a global reference to the list of games games = message.games; var template = '<tr data-game-id="{{ game.id }}"><td class="team home"><h3>{{game.home.team}}</h3></td><td class="score home"><div id="counter-{{game.id}}-home" class="flip-counter"></div></td><td class="divider"><p>:</p></td><td class="score away"><div id="counter-{{game.id}}-away" class="flip-counter"></div></td><td class="team away"><h3>{{game.away.team}}</h3></td></tr>'; $.each(games, function(id){ var game = games[id]; $('#scoreboard table').append(Mustache.render(template, {game:game} )); game.counter_home = new flipCounter("counter-"+id+"-home", {value: game.home.score, auto: false}); game.counter_away = new flipCounter("counter-"+id+"-away", {value: game.away.score, auto: false}); }); } 

In this function, we simply “run” through the array of games, using Mustache to render a new row to the score table and implement a pair of animated counters for each game. The games array will store the current state of the games and links to the counters so that we can update them as needed.
Next is the goal function. We receive a message through a web socket, which signals a state change, has the following structure:
 { type: 'goal', game: 'UNIQUE-ID', team: 'home' } 

The game property is a unique ID, the team is either “home” or “away”. Using this data, we can update the score in the games array, find the counter object we need and increase it.
 function goal(message) { games[message.game][message.team]['score']++; var counter = games[message.game]['counter_'+message.team]; counter.incrementTo(games[message.game][message.team]['score']); } 

Now, all we have left is to start the server from the command line:
 php bin/server.php 

Conclusion


In this article, I showed how easy it is to create a “Live account” widget using JS, HTML and web sockets. Of course, there’s usually a hunt to see much more information than just an account, but since we use JSON, we can easily add other data.
Demo .

')

Source: https://habr.com/ru/post/198954/


All Articles