CakePHP Functions

 

Building a Real-Time Chat Application with CakePHP and WebSockets

In today’s digital age, real-time communication is crucial for enhancing user engagement and experience. One of the most popular ways to achieve real-time messaging is by using WebSockets, a protocol that enables bi-directional communication between a server and clients. In this tutorial, we’ll explore how to build a real-time chat application using CakePHP, a powerful PHP framework, combined with the magic of WebSockets. Let’s dive in!

Building a Real-Time Chat Application with CakePHP and WebSockets

1. Prerequisites

Before we start, make sure you have the following prerequisites installed on your system:

  1. PHP 7.2 or higher
  2. Composer
  3. CakePHP 4.x or higher
  4. A modern web browser that supports WebSockets

2. Setting up the Project

First, let’s create a new CakePHP project. Open your terminal and run the following command:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
bash
composer create-project --prefer-dist cakephp/app realtime-chat-app
cd realtime-chat-app
bash composer create-project --prefer-dist cakephp/app realtime-chat-app cd realtime-chat-app
bash
composer create-project --prefer-dist cakephp/app realtime-chat-app
cd realtime-chat-app

3. Setting up the Database

For our real-time chat application, we need to set up a database to store messages. In this example, we’ll use a simple “messages” table. Run the following CakePHP migration command to create the table:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
bash
bin/cake bake migration CreateMessages
bash bin/cake bake migration CreateMessages
bash
bin/cake bake migration CreateMessages

In the generated migration file, modify the up() method to create the “messages” table:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
php
// config/Migrations/20230717000000_CreateMessages.php
use Cake\Database\Driver\Mysql;
use Migrations\AbstractMigration;
class CreateMessages extends AbstractMigration
{
public function change()
{
$table = $this->table('messages');
$table->addColumn('user_id', 'integer', ['null' => false])
->addColumn('content', 'text', ['null' => false])
->addColumn('created', 'datetime', ['default' => null])
->addIndex(['user_id'])
->create();
}
}
php // config/Migrations/20230717000000_CreateMessages.php use Cake\Database\Driver\Mysql; use Migrations\AbstractMigration; class CreateMessages extends AbstractMigration { public function change() { $table = $this->table('messages'); $table->addColumn('user_id', 'integer', ['null' => false]) ->addColumn('content', 'text', ['null' => false]) ->addColumn('created', 'datetime', ['default' => null]) ->addIndex(['user_id']) ->create(); } }
php
// config/Migrations/20230717000000_CreateMessages.php

use Cake\Database\Driver\Mysql;
use Migrations\AbstractMigration;

class CreateMessages extends AbstractMigration
{
    public function change()
    {
        $table = $this->table('messages');
        $table->addColumn('user_id', 'integer', ['null' => false])
              ->addColumn('content', 'text', ['null' => false])
              ->addColumn('created', 'datetime', ['default' => null])
              ->addIndex(['user_id'])
              ->create();
    }
}

Now, apply the migration to create the “messages” table:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
bash
bin/cake migrations migrate
bash bin/cake migrations migrate
bash
bin/cake migrations migrate

4. Creating the Chat Interface

Next, let’s set up the user interface for our real-time chat application. We’ll use HTML, CSS, and JavaScript for this purpose.

1. Create a new template file for the chat interface:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
bash
mkdir -p templates/Chat
touch templates/Chat/index.ctp
bash mkdir -p templates/Chat touch templates/Chat/index.ctp
bash
mkdir -p templates/Chat
touch templates/Chat/index.ctp

2. Add the following HTML structure to index.ctp:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
html
<!-- templates/Chat/index.ctp -->
<!DOCTYPE html>
<html>
<head>
<title>Real-Time Chat Application</title>
<!-- Add your CSS styles here -->
</head>
<body>
<div id="chat-box">
<div id="chat-messages"></div>
<div id="user-input">
<input type="text" id="message-input" placeholder="Type your message here...">
<button id="send-button">Send</button>
</div>
</div>
<!-- Add your JavaScript code here -->
</body>
</html>
html <!-- templates/Chat/index.ctp --> <!DOCTYPE html> <html> <head> <title>Real-Time Chat Application</title> <!-- Add your CSS styles here --> </head> <body> <div id="chat-box"> <div id="chat-messages"></div> <div id="user-input"> <input type="text" id="message-input" placeholder="Type your message here..."> <button id="send-button">Send</button> </div> </div> <!-- Add your JavaScript code here --> </body> </html>
html
<!-- templates/Chat/index.ctp -->

<!DOCTYPE html>
<html>
<head>
    <title>Real-Time Chat Application</title>
    <!-- Add your CSS styles here -->
</head>
<body>
    <div id="chat-box">
        <div id="chat-messages"></div>
        <div id="user-input">
            <input type="text" id="message-input" placeholder="Type your message here...">
            <button id="send-button">Send</button>
        </div>
    </div>
    <!-- Add your JavaScript code here -->
</body>
</html>

3. Now, let’s implement the necessary JavaScript code to handle WebSocket connections and message sending:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
html
<!-- templates/Chat/index.ctp -->
<!DOCTYPE html>
<html>
<head>
<title>Real-Time Chat Application</title>
<!-- Add your CSS styles here -->
</head>
<body>
<div id="chat-box">
<div id="chat-messages"></div>
<div id="user-input">
<input type="text" id="message-input" placeholder="Type your message here...">
<button id="send-button">Send</button>
</div>
</div>
<script>
const socket = new WebSocket('ws://localhost:8080');
// Handle WebSocket connection
socket.onopen = () => {
console.log('WebSocket connected');
};
// Handle incoming messages
socket.onmessage = (event) => {
const message = JSON.parse(event.data);
appendMessage(message.user_id, message.content, 'received');
};
// Send messages to the server
document.getElementById('send-button').addEventListener('click', () => {
const messageInput = document.getElementById('message-input');
const messageContent = messageInput.value.trim();
if (messageContent !== '') {
const message = {
user_id: 1, // Replace with the actual user ID
content: messageContent,
};
socket.send(JSON.stringify(message));
appendMessage(message.user_id, messageContent, 'sent');
messageInput.value = '';
}
});
// Function to display messages on the chat interface
function appendMessage(userId, content, messageType) {
const chatMessages = document.getElementById('chat-messages');
const messageDiv = document.createElement('div');
messageDiv.className = messageType;
messageDiv.innerText = `User ${userId}: ${content}`;
chatMessages.appendChild(messageDiv);
}
</script>
</body>
</html>
html <!-- templates/Chat/index.ctp --> <!DOCTYPE html> <html> <head> <title>Real-Time Chat Application</title> <!-- Add your CSS styles here --> </head> <body> <div id="chat-box"> <div id="chat-messages"></div> <div id="user-input"> <input type="text" id="message-input" placeholder="Type your message here..."> <button id="send-button">Send</button> </div> </div> <script> const socket = new WebSocket('ws://localhost:8080'); // Handle WebSocket connection socket.onopen = () => { console.log('WebSocket connected'); }; // Handle incoming messages socket.onmessage = (event) => { const message = JSON.parse(event.data); appendMessage(message.user_id, message.content, 'received'); }; // Send messages to the server document.getElementById('send-button').addEventListener('click', () => { const messageInput = document.getElementById('message-input'); const messageContent = messageInput.value.trim(); if (messageContent !== '') { const message = { user_id: 1, // Replace with the actual user ID content: messageContent, }; socket.send(JSON.stringify(message)); appendMessage(message.user_id, messageContent, 'sent'); messageInput.value = ''; } }); // Function to display messages on the chat interface function appendMessage(userId, content, messageType) { const chatMessages = document.getElementById('chat-messages'); const messageDiv = document.createElement('div'); messageDiv.className = messageType; messageDiv.innerText = `User ${userId}: ${content}`; chatMessages.appendChild(messageDiv); } </script> </body> </html>
html
<!-- templates/Chat/index.ctp -->

<!DOCTYPE html>
<html>
<head>
    <title>Real-Time Chat Application</title>
    <!-- Add your CSS styles here -->
</head>
<body>
    <div id="chat-box">
        <div id="chat-messages"></div>
        <div id="user-input">
            <input type="text" id="message-input" placeholder="Type your message here...">
            <button id="send-button">Send</button>
        </div>
    </div>
    <script>
        const socket = new WebSocket('ws://localhost:8080');

        // Handle WebSocket connection
        socket.onopen = () => {
            console.log('WebSocket connected');
        };

        // Handle incoming messages
        socket.onmessage = (event) => {
            const message = JSON.parse(event.data);
            appendMessage(message.user_id, message.content, 'received');
        };

        // Send messages to the server
        document.getElementById('send-button').addEventListener('click', () => {
            const messageInput = document.getElementById('message-input');
            const messageContent = messageInput.value.trim();

            if (messageContent !== '') {
                const message = {
                    user_id: 1, // Replace with the actual user ID
                    content: messageContent,
                };

                socket.send(JSON.stringify(message));
                appendMessage(message.user_id, messageContent, 'sent');

                messageInput.value = '';
            }
        });

        // Function to display messages on the chat interface
        function appendMessage(userId, content, messageType) {
            const chatMessages = document.getElementById('chat-messages');
            const messageDiv = document.createElement('div');
            messageDiv.className = messageType;
            messageDiv.innerText = `User ${userId}: ${content}`;
            chatMessages.appendChild(messageDiv);
        }
    </script>
</body>
</html>

4. Creating the WebSocket Server

Now that we have the chat interface set up, let’s create the WebSocket server using Ratchet, a PHP library that provides WebSocket support.

1. Install Ratchet via Composer:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
bash
composer require cboden/ratchet
bash composer require cboden/ratchet
bash
composer require cboden/ratchet

2. Create a new PHP file for the WebSocket server:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
bash
mkdir src/Chat
touch src/Chat/ChatServer.php
bash mkdir src/Chat touch src/Chat/ChatServer.php
bash
mkdir src/Chat
touch src/Chat/ChatServer.php

3. Implement the WebSocket server in ChatServer.php:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
php
// src/Chat/ChatServer.php
namespace App\Chat;
use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;
class ChatServer 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})\n";
}
public function onMessage(ConnectionInterface $from, $msg)
{
$data = json_decode($msg, true);
// You can handle the received message here, such as saving it to the database
foreach ($this->clients as $client) {
if ($from !== $client) {
$client->send($msg);
}
}
}
public function onClose(ConnectionInterface $conn)
{
$this->clients->detach($conn);
echo "Connection {$conn->resourceId} has disconnected\n";
}
public function onError(ConnectionInterface $conn, \Exception $e)
{
echo "An error occurred: {$e->getMessage()}\n";
$conn->close();
}
}
php // src/Chat/ChatServer.php namespace App\Chat; use Ratchet\MessageComponentInterface; use Ratchet\ConnectionInterface; class ChatServer 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})\n"; } public function onMessage(ConnectionInterface $from, $msg) { $data = json_decode($msg, true); // You can handle the received message here, such as saving it to the database foreach ($this->clients as $client) { if ($from !== $client) { $client->send($msg); } } } public function onClose(ConnectionInterface $conn) { $this->clients->detach($conn); echo "Connection {$conn->resourceId} has disconnected\n"; } public function onError(ConnectionInterface $conn, \Exception $e) { echo "An error occurred: {$e->getMessage()}\n"; $conn->close(); } }
php
// src/Chat/ChatServer.php

namespace App\Chat;

use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;

class ChatServer 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})\n";
    }

    public function onMessage(ConnectionInterface $from, $msg)
    {
        $data = json_decode($msg, true);

        // You can handle the received message here, such as saving it to the database

        foreach ($this->clients as $client) {
            if ($from !== $client) {
                $client->send($msg);
            }
        }
    }

    public function onClose(ConnectionInterface $conn)
    {
        $this->clients->detach($conn);
        echo "Connection {$conn->resourceId} has disconnected\n";
    }

    public function onError(ConnectionInterface $conn, \Exception $e)
    {
        echo "An error occurred: {$e->getMessage()}\n";
        $conn->close();
    }
}

4. To run the WebSocket server, create a new PHP file websocket-server.php:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
bash
touch websocket-server.php
bash touch websocket-server.php
bash
touch websocket-server.php

5. Implement the server code in websocket-server.php:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
php
// websocket-server.php
require dirname(__DIR__) . '/vendor/autoload.php';
use Ratchet\Server\IoServer;
use Ratchet\Http\HttpServer;
use Ratchet\WebSocket\WsServer;
use App\Chat\ChatServer;
$server = IoServer::factory(
new HttpServer(
new WsServer(
new ChatServer()
)
),
8080
);
echo "WebSocket server started\n";
$server->run();
php // websocket-server.php require dirname(__DIR__) . '/vendor/autoload.php'; use Ratchet\Server\IoServer; use Ratchet\Http\HttpServer; use Ratchet\WebSocket\WsServer; use App\Chat\ChatServer; $server = IoServer::factory( new HttpServer( new WsServer( new ChatServer() ) ), 8080 ); echo "WebSocket server started\n"; $server->run();
php
// websocket-server.php

require dirname(__DIR__) . '/vendor/autoload.php';

use Ratchet\Server\IoServer;
use Ratchet\Http\HttpServer;
use Ratchet\WebSocket\WsServer;
use App\Chat\ChatServer;

$server = IoServer::factory(
    new HttpServer(
        new WsServer(
            new ChatServer()
        )
    ),
    8080
);

echo "WebSocket server started\n";

$server->run();

5. Connecting the WebSocket Server to CakePHP

To connect the WebSocket server to our CakePHP application, we’ll use a shell process to run the WebSocket server alongside the CakePHP application.

1. Create a new shell command to start the WebSocket server:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
bash
mkdir src/Shell
touch src/Shell/ChatShell.php
bash mkdir src/Shell touch src/Shell/ChatShell.php
bash
mkdir src/Shell
touch src/Shell/ChatShell.php

2. Implement the shell command in ChatShell.php:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
php
// src/Shell/ChatShell.php
namespace App\Shell;
use Cake\Console\Shell;
class ChatShell extends Shell
{
public function main()
{
$this->out($this->OptionParser->help());
}
public function start()
{
$this->out('Starting WebSocket server...');
exec('php websocket-server.php');
}
}
php // src/Shell/ChatShell.php namespace App\Shell; use Cake\Console\Shell; class ChatShell extends Shell { public function main() { $this->out($this->OptionParser->help()); } public function start() { $this->out('Starting WebSocket server...'); exec('php websocket-server.php'); } }
php
// src/Shell/ChatShell.php

namespace App\Shell;

use Cake\Console\Shell;

class ChatShell extends Shell
{
    public function main()
    {
        $this->out($this->OptionParser->help());
    }

    public function start()
    {
        $this->out('Starting WebSocket server...');
        exec('php websocket-server.php');
    }
}

3. Now, we need to create a new route to access the chat interface and start the WebSocket server:

Open config/routes.php and add the following route:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
php
// config/routes.php
use Cake\Routing\Route\DashedRoute;
use Cake\Routing\RouteBuilder;
$routes->scope('/', function (RouteBuilder $builder) {
// Other routes...
// Add the chat route
$builder->connect('/chat', ['controller' => 'Chat', 'action' => 'index']);
});
$routes->fallbacks(DashedRoute::class);
php // config/routes.php use Cake\Routing\Route\DashedRoute; use Cake\Routing\RouteBuilder; $routes->scope('/', function (RouteBuilder $builder) { // Other routes... // Add the chat route $builder->connect('/chat', ['controller' => 'Chat', 'action' => 'index']); }); $routes->fallbacks(DashedRoute::class);
php
// config/routes.php

use Cake\Routing\Route\DashedRoute;
use Cake\Routing\RouteBuilder;

$routes->scope('/', function (RouteBuilder $builder) {
    // Other routes...

    // Add the chat route
    $builder->connect('/chat', ['controller' => 'Chat', 'action' => 'index']);
});

$routes->fallbacks(DashedRoute::class);

6. Final Touches

With everything in place, you can now run your CakePHP application and WebSocket server.

1. Run the WebSocket server in a separate terminal window:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
bash
bin/cake chat start
bash bin/cake chat start
bash
bin/cake chat start

2. Start your CakePHP application server:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
bash
bin/cake server
bash bin/cake server
bash
bin/cake server

Visit http://localhost:8765/chat in your web browser to access the real-time chat interface.

Conclusion

In this tutorial, we explored how to build a real-time chat application using CakePHP and WebSockets. By combining the power of CakePHP’s backend capabilities with the real-time communication provided by WebSockets, you can create a feature-rich and engaging chat experience for your users. Now that you have the foundation, feel free to enhance the application further by adding features like user authentication, message history, and more. Happy coding!

blank
Previously at
blank
Flag Argentina
Brazil
time icon
GMT-3
Experienced AI enthusiast with 5+ years, contributing to PyTorch tutorials, deploying object detection solutions, and enhancing trading systems. Skilled in Python, TensorFlow, PyTorch.