Background
Break News
How to add local font to Tailwind Css and NextJS? - Tutorial Design Pattern? - Blockchain Technology, How to create own Bitcoin virtual currency - Zustand mordern management state - Design Pattern - Flyweight Pattern? - Docker Full training Topic

[Tutorial] How to implement Ratchet Socket on cakephp 4 and make a detail demo broadcast message from server to client (Part 1)

Friday 3 November 2023
|
Read: Completed in minutes

[Tutorial] How to implement Ratchet Socket on cakephp 4 and make a detail demo broadcast message from server to client (Part 1)

Ratchet is a PHP library that allows you to create real-time web applications using WebSockets. WebSockets are a bidirectional communication protocol that enable the server and the client to exchange messages without the need for HTTP requests and responses. In this article, I will show you how to implement Ratchet Socket on CakePHP 4.4.2, and demonstrate how to broadcast a message from the server to the client, and how to receive a message from the client on the server.

How to implement Ratchet Socket on cakephp 4 and make a detail demo broadcast message from server to client (Part 1)

CakePHP is a popular PHP framework that follows the Model-View-Controller (MVC) pattern. It provides many features and conventions that make web development easier and faster. To use Ratchet with CakePHP, we need to install some dependencies and create some files.

How to implement Ratchet Socket on cakephp 4 and make a detail demo broadcast message from server to client (Part 1)


First, we need to install Ratchet and ReactPHP using Composer. Ratchet depends on ReactPHP, which is a low-level library for event-driven programming in PHP. To install them, run the following command in your CakePHP project directory:

composer require cboden/ratchet

Next, we need to create a WebSocket server class that will handle the WebSocket connections and messages. We can use the Ratchet\MessageComponentInterface interface to implement the required methods: onOpen, onMessage, onClose, and onError. The onOpen method is called when a new connection is established, the onMessage method is called when a message is received from a connection, the onClose method is called when a connection is closed, and the onError method is called when an error occurs on a connection. We can also use the Ratchet\ConnectionInterface interface to represent a WebSocket connection.

Let's create a file named RatchetServerCommand.php in the src/Command directory of our CakePHP project, and write the following code:  

<?php

namespace App\Command;

use Cake\Command\Command;
use Cake\Console\Arguments;
use Cake\Console\ConsoleIo;
use Cake\ORM\TableRegistry;

use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;
use Cake\Console\ConsoleOptionParser;
use Cake\Log\Log;

// buildOptionParser => bin/cake Ratchet -p 8000
class RatchetServerCommand extends Command implements MessageComponentInterface
{
// A property to store all connected clients
protected $clients;
protected $rooms;

protected $room = 'MetroSouthGroup';
protected $log = "websocket_logs";

public function __construct()
{
parent::__construct();
$this->clients = new \SplObjectStorage;
$this->rooms = [];
}

protected function write_log($msg) {
Log::info($msg, ['scope' => $this->log]);
}

public function initialize(): void
{
parent::initialize();
}

// A method to define the command name and options
protected function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionParser
{
$parser //->setName('chat')
->setDescription('Start a Webserver using Ratchet.')
->addOption('port', [
'help' => 'The port number to listen on.',
'default' => 8080,
'short' => 'p'
]);
return $parser;
}


// A method to run the command logic
public function execute(Arguments $args, ConsoleIo $io): int
{
$port = $args->getOption('port');

// Create an instance of WsServer and pass $this as the message component
$wsServer = new \Ratchet\WebSocket\WsServer($this);

// Create an instance of HttpServer and pass the WsServer instance
$httpServer = new \Ratchet\Http\HttpServer($wsServer);

// Create an IoServer instance from the HttpServer instance and the port number
$server = \Ratchet\Server\IoServer::factory($httpServer, $port);

// Print a message
$io->out("Starting server on port {$port}...");

// Run the server
$server->run();

// Return success code
return static::CODE_SUCCESS;
}

protected function addCorsHeaders(ConnectionInterface $conn) {
$headers = [
'Access-Control-Allow-Origin' => '*',
'Access-Control-Allow-Methods' => 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers' => 'Origin, X-Requested-With, Content-Type, Accept'
];
foreach ($headers as $key => $value) {
$conn->send(sprintf("%s: %s\r\n", $key, $value));
}
}

// A method to handle new connections
public function onOpen(ConnectionInterface $conn)
{
// Store the new connection in the clients property
$this->clients->attach($conn);

// Send a welcome message to the new connection
$data = [
'type' => 'message',
'content' => 'Welcome to our server',
];
$conn->send(json_encode( $data ));

// Add CORS headers to the response
$this->addCorsHeaders($conn);

// Log the new connection
$msg = "New connection! ({$conn->resourceId})\n";
$this->write_log($msg);
echo $msg;
}

// A method to handle closed connections
public function onClose(ConnectionInterface $conn)
{
// Remove the closed connection from the clients property
$this->clients->detach($conn);

// Log the closed connection
$msg = "Connection {$conn->resourceId} has disconnected\n";
$this->write_log($msg);
echo $msg;
}

// A method to handle errors
public function onError(ConnectionInterface $conn, \Exception $e)
{
// Log the error message
$msg = "An error has occurred: {$e->getMessage()}\n";
$this->write_log($msg);

// Close the connection
$conn->close();
}

// rooms
public function onMessage(ConnectionInterface $from, $msg)
{
// Decode the message
$data = json_decode($msg, true);

if (!isset($data['cmd']) || empty($data['cmd']) ) {
$this->send_error($from, "Missing cmd");

} else {
$room = $this->room; /// will let client assign, this time will send group, server control this

switch ($data['cmd']) {
case 'join':

if (!isset($data['language']) || empty($data['language']) ) {
$this->send_error($from, "Missing language");

} elseif (!isset($data['token']) || empty($data['token']) ) {
$this->send_error($from, "Missing token");

} else {
$this->join_room($from, $room, $data['language'], $data['token']);

}
break;

case 'create':
$this->create_room($from, $room);
break;

case 'broadcast':

if (!isset($data['language']) || empty($data['language']) ) {
$this->send_error($from, "Missing language");

} else {
$this->broadcast_message( $data['language'] );
}
break;

default:
// Invalid command
$this->send_error($from, "Invalid command");
}
}
}

private function get_message_from_db($language, $token){
$obj_SystemMessages = TableRegistry::get('SystemMessages');
return $obj_SystemMessages->get_latest_content_without_read($language, $token);
}

// data = ['language' => '', 'token' => '', 'cmd' => 'join' ]
protected function join_room(ConnectionInterface $user, $room, $language, $token)
{
// Check if the room exists
if (isset($this->rooms[$room])) {
// Add the user to the room
$this->rooms[$room][$user->resourceId] = $user;

// Send a welcome message to the new connection
$data = $this->get_message_from_db($language, $token);

$this->send_notification_to_client($user, $data);

} else { // Room does not exist

$this->create_room($user, $room);
$this->join_room($user, $room, $language, $token);
}
}

protected function create_room(ConnectionInterface $user, $room)
{
// Check if the room already exists
if (!isset($this->rooms[$room])) {
// Create the room as an empty array
$this->rooms[$room] = [];

$msg = "Create Room succeed";
$this->write_log($msg);
echo $msg;

} else {
// Room already exists
$msg = "Room {$room} already exists";
$this->send_error($user, $msg);
$this->write_log($msg);
echo $msg;
}
}

// send to client
protected function send_notification_to_client(ConnectionInterface $user, $data) {

// Send the message to the user
$user->send(json_encode($data));
}

protected function send_error(ConnectionInterface $user, $message)
{
// Encode the message as JSON
$data = json_encode(['type' => 'error', 'content' => $message]);
$user->send($data);
}

public function broadcast_message($language)
{
$obj_SystemMessages = TableRegistry::get('SystemMessages');
$data = $obj_SystemMessages->get_latest_content_broadcast($language);

$i = 0;
foreach ($this->clients as $client) {
$this->send_notification_to_client($client, $data);
$i++;
}

$msg = "Total Client connected: " . $i;
$this->write_log($msg);
}
}

To test the WebSocket server, we need to create a client that will connect to it and send and receive messages. We can use any WebSocket client library or tool for this purpose. For example, we can use the [Simple WebSocket Client] extension for Chrome or Firefox.
 

https://chrome.google.com/webstore/detail/simple-websocket-client/pfdhoblngboilpfeibdedpjgfnlcodoo


To use this extension, open a new tab in your browser and click on the Simple WebSocket Client icon. Then, enter the WebSocket server URL in the input field, which is ws://localhost:8080 in our case. Then, click on the Open button to establish a connection. You should see a message like this:

How to implement Ratchet Socket on cakephp 4 and make a detail demo broadcast message from server to client (Part 1)


On the next part I will share with you detail code from client side and connect to server.

Thank you for reading this post. I hope you found it helpful and easy to follow. If you have any feedback or questions about [Tutorial] How to implement Ratchet Socket on cakephp 4 and make a detail demo broadcast message from server to client (Part 1) , please share them in the comments below. I would love to hear from you and discuss this topic further
✋✋✋✋  Webzone Tech Tips, all things Tech Tips for web development  - I am Zidane, See you next time soon ✋✋✋✋

🙇🏼 We Appreciate Your Comments and Suggestions - Webzone - all things Tech Tips web development 🙇🏼
Popular Webzone Tech Tips topic maybe you will be like it - by Webzone Tech Tips - Zidane
As a student, I found Blogspot very useful when I joined in 2014. I have been a developer for years . To give back and share what I learned, I started Webzone, a blog with tech tips. You can also search for tech tips zidane on Google and find my helpful posts. Love you all,

I am glad you visited my blog. I hope you find it useful for learning tech tips and webzone tricks. If you have any technical issues, feel free to browse my posts and see if they can help you solve them. You can also leave a comment or contact me if you need more assistance. Here is my blog address: https://learn-tech-tips.blogspot.com.

My blog where I share my passion for web development, webzone design, and tech tips. You will find tutorials on how to build websites from scratch, using hot trends frameworks like nestjs, nextjs, cakephp, devops, docker, and more. You will also learn how to fix common bugs on development, like a mini stackoverflow. Plus, you will discover how to easily learn programming languages such as PHP (CAKEPHP, LARAVEL), C#, C++, Web(HTML, CSS, javascript), and other useful things like Office (Excel, Photoshop). I hope you enjoy my blog and find it helpful for your projects. :)

Thanks and Best Regards!
Follow me on Tiktok @learntechtips and send me a direct message. I will be happy to chat with you.
Webzone - Zidane (huuvi168@gmail.com)
I'm developer, I like code, I like to learn new technology and want to be friend with people for learn each other
I'm a developer who loves coding, learning new technologies, and making friends with people who share the same passion. I have been a full stack developer since 2015, with more than years of experience in web development.
Copyright @2022(November) Version 1.0.0 - By Webzone, all things Tech Tips for Web Development Zidane
https://learn-tech-tips.blogspot.com