Ratchet WebSocket Chat Application Documentation

Ratchet WebSocket, along with Prisma PHP, empowers developers to build real-time chat applications effortlessly. Ratchet is a PHP library that simplifies the process of creating bi-directional communication between clients and servers using WebSockets. It provides an intuitive interface for messaging, event handling, and connection management, making it perfect for developing interactive applications like live chat systems.

Prisma PHP enhances the development experience by automating server setup and introducing an auto-refresh feature that detects file changes. This seamless integration streamlines the development workflow, allowing developers to focus more on building features and less on tedious setup tasks. By combining the power of Ratchet WebSocket with the convenience of Prisma PHP, developers can create chat applications with ease, providing users with a seamless real-time communication experience.

Chat Application

Describes the frontend part of the chat application, detailing the HTML and JavaScript necessary for PHP file real-time communication.

This Chat application use Tailwindcss for styling and WebSocket for real-time communication.

Create your custom rout ./src/app/chatapp/index.php then paste the code below

<div class="grid place-items-center w-screen h-screen">
      <div class="space-y-2">
          <ul id="messages" class="max-h-96 overflow-y-scroll p-4 list-disc space-y-2">
              <!-- Messages will be appended here -->
          </ul>
          <form id="messageForm" class="flex flex-col gap-2">
              <label id="userId">User ID</label>
              <input class="border rounded-lg p-3" type="text" id="recipientInput" placeholder="Recipient ID...">
              <div>
                  <input class="border rounded-lg p-3" type="text" id="messageInput" placeholder="Type a message...">
                  <button type="submit" class="p-3 border bg-blue-500 rounded-lg text-white">Send</button>
              </div>
          </form>
      </div>
  </div>
  
  <script>
      let userId = null; // Variable to store the client's ID
  
      ws.onopen = function(event) {
          console.log("Connected to the WebSocket server");
      };
  
      ws.onmessage = function(event) {
          try {
              const data = JSON.parse(event.data);
  
              switch (data.type) {
                  case "init":
                      userId = data.senderId.toString(); // Ensuring userId is a string
                      console.log(`User ID set to ${userId}`);
                      document.getElementById('userId').textContent = `User ID: ${userId}`;
                      break;
                  case "broadcast":
                      displayMessage(data.message, 'received', data.senderId);
                      break;
                  case "private":
                      if (data.recipientId && data.recipientId.toString() === userId) {
                          displayMessage(data.message, 'received', data.senderId);
                      }
                      break;
                  default:
                      console.log("Unhandled message type.");
              }
          } catch (e) {
              console.error("Error parsing JSON data:", e.message);
              displayMessage(event.data, 'received', "Error");
          }
      };
  
      function displayMessage(message, type, senderId = "System") {
          const messages = document.getElementById('messages');
          const messageElement = document.createElement('li');
          let textContent = type === 'received' ? `From ${senderId}: ${message}` : `You: ${message}`;
          messageElement.textContent = textContent;
        messageElement.className = type === 'sent' ? 'bg-green-500 text-white p-2 rounded-lg' : 'bg-blue-500 text-white p-2   rounded-lg';
          messages.appendChild(messageElement);
  
          messages.scrollTop = messages.scrollHeight; // Scroll to the bottom of the messages
      }
  
      document.getElementById('messageForm').addEventListener('submit', function(event) {
          event.preventDefault();
          const recipientInput = document.getElementById('recipientInput').value.trim();
          const messageInput = document.getElementById('messageInput').value.trim();
          
          if (!messageInput) return; // Prevent sending empty messages
  
          const messageType = recipientInput ? 'private' : 'broadcast';
          const messagePayload = JSON.stringify({
              recipientId: recipientInput,
              message: messageInput,
              type: messageType
          });
  
          ws.send(messagePayload);
          displayMessage(messageInput, 'sent');
          document.getElementById('messageInput').value = ''; // Clear input field after sending
      });
  </script>

WebSocket Connection Management

Explains the role of the ConnectionManager class in handling connections, messaging, and errors within the WebSocket server environment.

Location in Prisma PHP ./src/lib/websocket/ConnectionManager.php then replace with the code below

<?php
        
  namespace Lib\Websocket;
  
  use Ratchet\MessageComponentInterface;
  use Ratchet\ConnectionInterface;
  
  class ConnectionManager implements MessageComponentInterface
  {
      protected $clients;
  
      public function __construct()
      {
          $this->clients = new \SplObjectStorage;
      }
  
      public function onOpen(ConnectionInterface $conn)
      {
          $this->clients->attach($conn);
          echo "New connection! ({$conn->resourceId})";
  
          $welcomeMessage = json_encode([
              'type' => 'init',
              'message' => 'Welcome to the WebSocket server!',
              'senderId' => $conn->resourceId
          ]);
          $conn->send($welcomeMessage);
      }
  
      public function onMessage(ConnectionInterface $from, $msg)
      {
          echo "New message from {$from->resourceId}!\n";
          echo "Message received: {$msg}";
  
          $data = json_decode($msg, true);
          $recipientId = isset($data['recipientId']) ? (string) $data['recipientId'] : null;
          $message = $data['message'] ?? '';
  
          if (!is_null($recipientId) && $recipientId !== "") {
              // Send to a specific recipient
              foreach ($this->clients as $client) {
                  if ((string) $client->resourceId === $recipientId) {
                      $client->send(json_encode([
                          'senderId' => $from->resourceId,
                          'recipientId' => $recipientId,
                          'message' => $message,
                          'type' => 'private'
                      ]));
                      // Reflect to sender
                      $from->send(json_encode([
                          'message' => $message,
                          'type' => 'ack',
                          'status' => 'sent'
                      ]));
                      return;
                  }
              }
              // If recipient not found, send error to sender
              $from->send(json_encode([
                  'type' => 'error',
                  'message' => 'Recipient not found',
                  'status' => 'error'
              ]));
          } else {
              // Broadcast message to all clients except the sender
              foreach ($this->clients as $client) {
                  if ($from !== $client) {
                      $client->send(json_encode([
                          'senderId' => $from->resourceId,
                          'message' => $message,
                          'type' => 'broadcast'
                      ]));
                  }
              }
          }
      }
  
      public function onClose(ConnectionInterface $conn)
      {
          $this->clients->detach($conn);
          echo "Connection {$conn->resourceId} has disconnected";
      }
  
      public function onError(ConnectionInterface $conn, \Exception $e)
      {
          echo "An error has occurred: {$e->getMessage()}";
          $conn->close();
      }
  }