WebSocket with PHP
Published: 2022-05-06 20:25:58

---
title: WebSocket with PHP
description: In this article, I will show you how to implement WebSocket service with PHP.
published: true
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qmlxilq3kmp0tbgd36oe.png
---

## The Web Socket scenario

PHP is typically used as a server-side language for responding to HTTP requests. Requests of this type are not persistent and are stateless. This scenario is straightforward: the client (the browser) makes a request (GET POST or PUT...) for a specific resource. The server, once provided with the resource, closes the connection. If it needs a further resource, the client starts a request again. A flow of this type is not persistent and controlled by the client.

In a WebSocket "scenario", you have a server-side part and multiple clients. A client can connect with the server-side part; if a client wants to communicate with other clients, it can do it by sending messages to the server. The server will forward or send messages to the client.
The connection between the client and the server is persistent and allows for a two-way conversation for multiple messages.

![WebSocket with PHP schema architecture](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mhdau42ypoehfyj9fyc5.png)

### Overview of the Server-Side Logic

Before we get to the code, let me explain the example application we will implement.
We have a server-side service implemented with PHP and Open Swoole. We will use "Swoole WebSocket Server" based on TCP protocol and secure WebSocket (network communication is encrypted, a bit like HTTP and HTTPS).
On the server side, we are going to implement a WebSocket service on TCP protocol that listens and reacts to some particular events:

- Start: when the server side WebSocket part is started;
- Open: when a new connection request from a new client arrives
- Message: when a message arrives from one of the active connections. Connections initialized with "Open" remain active and are persistent;
- Close: when a client closes the connection. The closing of the connection by the client implies the invalidation of its specific connection;
- Disconnect: when a client disconnects.

The server will keep track of the connected client via the Swoole Table. Swoole Table is a data structure similar to a collection (bi-dimensional array) that allows the management of multiple concurrent threads that want to add, remove, and retrieve items. In a typical PHP application, you don’t need that because a single thread accesses the collection or the data structure.

Once the Server receives a new message from a connected client (via “Message” event), the server will loop all the clients connected (stored in the Swoole Table data structure) and deliver the message to all clients (via push method).

### Overview of the Frontend Side Logic

The front-end logic is straightforward. We will create a simple HTML file (with no style, sorry for that) with an input text to allow the user to fill in the message and a button to send the message through the WebSocket. This part is covered by a JS script inline in the HTML file.

We will open a connection with the `WebSocket` class, and we will implement some callback:

- _onmessage_ : for managing when the message is received on the client;
- *onopen* : form managing when the WebSocket connection (with the server) is established;
- *onclose*: for managing when the WebSocket connection is closed;

When the user clicks the button, a function *sendMessage()* will be called. The function will be called the `send()` method from the WebSocket object to push the message to the server.

## Building a Web Socket example

### Installing Open Swoole

To implement WebSocket with PHP, you must install an additional module like Swoole. You have more than one module that allows you to implement WebSocket service in PHP. In this tutorial, I will use Open Swoole implementation by SwooleLabs, considering that Open Swoole includes support for WebSocket.

Open Swoole is released via PECL package so you can install it with PECL install.

```bash
pecl install -f -D 'enable-openssl="no" enable-sockets="no" enable-http2="no" enable-mysqlnd="no" enable-hook-curl="no" with-postgres="no"' openswoole
```

With -D option, you can specify the option to enable. If you want to allow Secure WebSocket, I suggest enabling “enable-openssl”.

The -f option is to force reinstalling the package if you have already installed it in the past.

### Installing composer package

With the latest version (22) of Open Swoole, you can use the standard packages (PSR) provided by Open Swoole. So you have to install the `openswoole/core` package:

```
composer require openswoole/core:22.1.5
```

Or, if you want to install the latest stable version, you can run:

```
composer require openswoole/core
```

### Installing the IDE Helper (optional)

If your editor like VS Code or Zed doesn't recognize the syntax of OpenSwoole classes or methods you should consider to install the OpenSwoole IDE-Helper package.

![The editor doesn't recognize the OpenSwoole syntax](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0dovhwfooi688op2z7r1.png)

The OpenSwoole IDE Helper package provides comprehensive IDE help files, enabling accurate autocompletion and enhancing your overall development experience.

> The OpenSwoole IDE-Helper package: https://packagist.org/packages/openswoole/ide-helper

To install the OpenSwoole IDE Helper package, you can use Composer. Run the following command in your project directory:

```shell
composer require openswoole/ide-helper --dev
```

After installing the IDE Helper, your editor will recognize the syntac of OpenSwoole elements.

![The Editor with OpenSwoole IDE Helper](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p2gomni0vkrx5gr2cxj0.png)





### Using the SSL certificates (optional)

If you want to establish a **secure** WebSocket connection and during the installation, you use the `enable-sockets="yes" ` option, you need:

- create private and public certificates
- set up correctly the WebSocket service

Let me start with creating the new certificates:

```bash
mkcert localhost 127.0.0.1 ::1
```

Two files are created:

- localhost+2-key.pem the SSL key file
- localhost+2.pem the SSL cert file

These two files will be loaded while you instance the WebSocket server class.

### The server-side code

Create a new file named *websocket.php.*

Let’s start by including the right classes. From the version 22 of OpenSwoole, you can use the OpenSwoole namespace:

```php
<?php

use OpenSwoole\WebSocket\{Frame, Server};
use OpenSwoole\Constant;
use OpenSwoole\Http\Request;
use OpenSwoole\Table;
```

Create the new `Server` instance on port `9501`, listening on `0.0.0.0` (accepting all incoming requests) using TCP protocol (`Constant::SOCK_TCP`). If you want to enable Secure WebSocket, you should use `Constant::SSL`
as forth parameter `Constant::SOCK_TCP || Constant::SSL` )

```php
$server = new Server("0.0.0.0", 9501, Server::SIMPLE_MODE, Constant::SOCK_TCP);
```

For storing the list of the client connected to the WebSocket, create a Table (a two dimensions memory table) with *fd* (file descriptor) and *name* fields:

```php
$fds = new Table(1024);
$fds->column('fd', Table::TYPE_INT, 4);
$fds->column('name', Table::TYPE_STRING, 16);
$fds->create();
```

If you are going to create a **secure** WebSocket connection, you have to configure the server to use the SSL certificates:

```php
$server->set([
    'ssl_cert_file' => __DIR__ . '/localhost+2.pem',
    'ssl_key_file' => __DIR__ . '/localhost+2-key.pem'
]);
```

Before you start the Server, you have to define the handler functions for the events dispatched by the WebSocket server.

#### Listening the _Start_ event.

The "Start" event is triggered once the WebSocket service is started.

```php
$server->on("Start", function (Server $server) {
    echo "Swoole WebSocket Server is started at " . $server->host . ":" . $server->port . "\n";
});
```

#### Listening the _Open_ event.
The "Open" event is triggered once a client is connected.

```php
$server->on('Open', function (Server $server, Request $request) use ($fds) {
    $fd = $request->fd;
    $clientName = sprintf("Client-%'.06d\n", $request->fd);
    $fds->set($request->fd, [
        'fd' => $fd,
        'name' => sprintf($clientName)
    ]);
    echo "Connection <{$fd}> open by {$clientName}. Total connections: " . $fds->count() . "\n";
    foreach ($fds as $key => $value) {
        if ($key == $fd) {
            $server->push($request->fd, "Welcome {$clientName}, there are " . $fds->count() . " connections");
        } else {
            $server->push($key, "A new client ({$clientName}) is joining to the party");
        }
    }
});
```

#### Listening the _Message_ event.
The "Message" event is triggered once a client sends a message to the WebSocket service.

```php
$server->on('Message', function (Server $server, Frame $frame) use ($fds) {
    $sender = $fds->get(strval($frame->fd), "name");
    echo "Received from " . $sender . ", message: {$frame->data}" . PHP_EOL;
    foreach ($fds as $key => $value) {
        if ($key == $frame->fd) {
            $server->push($frame->fd, "Message sent");
        } else {
            $server->push($key,  "FROM: {$sender} - MESSAGE: " . $frame->data);
        }
    }
});
```

#### Listening the _Close_ event.
The "Close" event is triggered once a client closes the connection.

```php
$server->on('Close', function (Server $server, int $fd) use ($fds) {
    $fds->del($fd);
    echo "Connection close: {$fd}, total connections: " . $fds->count() . "\n";
});
```

#### Listening the _Disconnect_ event.
The "Disconnect" event is triggered once a client loses the connection.

```php
$server->on('Disconnect', function (Server $server, int $fd) use ($fds) {
    $fds->del($fd);
    echo "Disconnect: {$fd}, total connections: " . $fds->count() . "\n";
});
```

#### Starting the service

Start the WebSocket server with the `start()` method. Once the server is started, the "_Start_" event is triggered.

```php
$server->start();
```

### Launching the Web Socket service

From the command line, you can launch the new service: 

```bash
php websocket.php
```


![Launching WebSocket PHP Service](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sxejyj5l7nbsj0ys7htk.png)

## Frontend code

Create a new _index.html_ file.

The code is quite simple. If you set it up on the server side and want to use the Secure Web socket connection, you must use the `wss://` protocol instead of the `ws://` protocol.

When you instance the WebSocket object, be sure to use the correct IP address (or hostname) and the correct port (the same port that you defined in the PHP script)

```html
<!doctype html>
<html>
<head>
    <title> WebSocket with PHP and Open Swoole </title>
    <script>
        let echo_service;
        append = function (text) {
            document.getElementById("websocket_events").insertAdjacentHTML('beforeend',
                "<li>" + text + ";</li>"
            );
        }
        window.onload = function () {
            echo_service = new WebSocket('ws://127.0.0.1:9501');
            echo_service.onmessage = function (event) {
                append(event.data)
            }
            echo_service.onopen = function () {
                append("Connected to WebSocket!");
            }
            echo_service.onclose = function () {
                append("Connection closed");
            }
            echo_service.onerror = function () {
                append("Error happens");
            }
        }

        function sendMessage(event) {
            console.log(event)
            let message = document.getElementById("message").value;
            echo_service.send(message);
        }

    </script>
</head>

<body>
    <div>
        Message: <input value="Hello!" type="text" id="message" /><br><br>
        <input type="button" value="Submit" onclick="sendMessage(event)" /><br>
        <ul id="websocket_events">
        </ul>
    </div>
</body>

</html>
```

If you open or access to HTML file with your browser (on multiple tab to emulate multiple clients), you can start to chat with yourself ;)


![Realtime Chat with PHP and WebSocket](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/totncb1yw91uk1ktb7ry.png)

## Source Code
I published the code used in this post on GitHub: https://github.com/roberto-butti/websocket-php

## Troubleshooting
Sometimes, during installation or compilation, you may encounter an error indicating that the 'pcre2.h' file was not found.

This typically happens when the PCRE (Perl Compatible Regular Expressions) library is missing or improperly configured on your system. Here's how you can solve this issue with your MacOS.

The first check you can perform is to check if you have installed the `pcre2` library.
If you are using Homebrew you can check:

```
brew info pcre2
```

If the Pcre2 package is installed, one of the most common reasons for the issue is the pcre2.h (the C source header file for pcre2) is not in the expected directory.

The compiling process requests the file of the OpenSwoole module.
The file is probably in the Pcre2 directories, but the OpenSwoole compiling process will look for that file between the PHP files. So one of the ways is to create a symbolic link from the PHP directory to the Pcre2 directory:

```
ln -s /opt/homebrew/Cellar/pcre2/10.42/include/pcre2.h /opt/homebrew/Cellar/php/8.3.3/include/php/ext/pcre/pcre2.h
```

In the command, I'm using the link tool `ln`, the `-s` flag creates a symbolic link, and I'm using the `10.42` version of pcre2 and the `8.3.3` version of PHP. You have to replace the current versions of Pcre2 and PHP.

After executing the command, you will find a symbolic link in the PHP directory `/opt/homebrew/Cellar/php/8.3.3/include/php/ext/pcre/pcre2.h`, so that you can execute the `pecl` command for installing the OpenSwoole module.