micilini/php-websockets

Native PHP WebSocket and realtime chat library built from scratch. Works standalone or with Laravel.

Maintainers

Package info

github.com/micilini/php-websockets

pkg:composer/micilini/php-websockets

Statistics

Installs: 3

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v1.0.2 2026-05-10 00:32 UTC

This package is auto-updated.

Last update: 2026-05-10 00:55:40 UTC


README

Native PHP WebSocket and realtime chat foundation built from scratch with PHP sockets. Works standalone with plain PHP or inside Laravel applications.

PHP 8.2+ Laravel 10/11/12/13 MIT License Tests Packagist

Pure PHP WebSockets. Realtime chat. Private rooms. Bots. Laravel-ready.

Application Images

Image 1 Image 2 Image 3 Image 4

Overview

PHPSockets started in 2016 as an educational experiment demonstrating how to build a WebSocket server directly with PHP sockets, without Node.js, without socket.io, and without a third-party realtime runtime.

The project has now been rebuilt as a modern Composer package for PHP 8.2+, with a clean architecture, WebSocket protocol primitives, a realtime server runtime, a chat kit, private conversations, private group rooms, small attachments, emoji-safe payloads, bot hooks, optional storage adapters, and Laravel integration.

The goal is not only to provide a chat example.

The goal is to provide a native PHP realtime foundation that developers can use to build:

  • realtime chat widgets;
  • support chat systems;
  • private rooms;
  • collaborative dashboards;
  • notification servers;
  • internal realtime tools;
  • chatbot-ready messaging flows;
  • Laravel-powered realtime applications.

Features

  • Native WebSocket core — handshake, frames, opcodes, close codes, ping/pong and payload validation.
  • Pure PHP socket runtime — no socket.io, Ratchet, ReactPHP, Swoole, Workerman or Node.js required.
  • Realtime server layer — connection registry, lifecycle events, message dispatching and safe closing.
  • Chat Kit — sessions, unique display names, presence, global messages, direct messages and rooms.
  • EasyChat example — simple global chat for beginners.
  • MediumChat example — callback/event-driven chat for customization.
  • PrivateChat example — global room, direct 1:1 conversations, private group rooms and unread badges.
  • Private group rooms — create a room with selected online users only.
  • Typing indicators — user feedback for active conversations.
  • Message receipts — simple sent/received/read states in examples.
  • Emoji support — safe UTF-8 text payloads with a built-in emoji picker in examples.
  • Small attachments — images, PDFs and text files up to the configured limit.
  • Downloadable file messages — delivered attachments include a download action.
  • Fragmented text frame support — large JSON text messages can be reassembled safely.
  • Storage adapters — in-memory, file JSONL messages and PDO-based SQL storage.
  • Database migrations — SQLite, MySQL and PostgreSQL schemas through the migration runner.
  • Bot hooks — register lightweight bots that can respond in global, direct and private group contexts.
  • Laravel integration — Service Provider, Facade, publishable config and Artisan commands.
  • Quality tooling — PHPUnit, PHPStan, PHP CS Fixer and GitHub Actions.
  • Legacy preserved — the original 2016 EasyChat and MediumChat implementations are kept for historical reference.

Installation

Install through Composer:

composer require micilini/php-websockets

For local development:

git clone https://github.com/micilini/php-websockets.git
cd php-websockets
composer install

Requirements

Required:

  • PHP 8.2 or higher;
  • ext-sockets;
  • ext-json;
  • Composer.

Optional:

  • ext-pdo for SQL storage adapters;
  • ext-pdo_sqlite for SQLite storage and tests;
  • Laravel 10, 11, 12 or 13 for Laravel integration.

Quick Start

Standalone PHP server

Create a simple WebSocket chat server:

<?php

declare(strict_types=1);

require __DIR__ . '/vendor/autoload.php';

use Micilini\PhpSockets\Chat\ChatServer;
use Micilini\PhpSockets\Config\ChatConfig;
use Micilini\PhpSockets\Config\ServerConfig;

$server = ChatServer::create(
    ServerConfig::new(
        host: '127.0.0.1',
        port: 8080,
        maxPayloadBytes: 4 * 1024 * 1024,
        enableDebugLogs: true,
    ),
    ChatConfig::new(
        maxAttachmentBytes: 2 * 1024 * 1024,
    ),
);

$server->run();

Run it:

php server.php

The WebSocket server will listen on:

ws://127.0.0.1:8080

Running the Examples

The example servers can be executed both from a cloned repository and from a project where the package was installed through Composer. The examples use examples/bootstrap.php to locate the correct Composer autoload file automatically.

Each example has two parts:

  1. a WebSocket server process;
  2. a browser UI served by PHP's built-in HTTP server.

EasyChat

Start the WebSocket server:

php examples/easy-chat/server.php

Open another terminal and serve the UI:

php -S 127.0.0.1:8000 -t examples/easy-chat/public

If you installed the package inside another project with Composer, run:

php vendor/micilini/php-websockets/examples/easy-chat/server.php
php -S 127.0.0.1:8000 -t vendor/micilini/php-websockets/examples/easy-chat/public

Open:

http://127.0.0.1:8000

EasyChat demonstrates:

  • global chat;
  • unique display names;
  • presence;
  • typing indicators;
  • emoji picker;
  • small attachments;
  • safe message rendering.

MediumChat

Start the WebSocket server:

php examples/medium-chat/server.php

Open another terminal and serve the UI:

php -S 127.0.0.1:8001 -t examples/medium-chat/public

If you installed the package inside another project with Composer, run:

php vendor/micilini/php-websockets/examples/medium-chat/server.php
php -S 127.0.0.1:8001 -t vendor/micilini/php-websockets/examples/medium-chat/public

Open:

http://127.0.0.1:8001

MediumChat demonstrates everything from EasyChat plus:

  • server-side callbacks;
  • low-level socket events;
  • chat lifecycle logs;
  • event panel in the browser;
  • extensibility points for custom behavior.

PrivateChat

Start the WebSocket server:

php examples/private-chat/server.php

Open another terminal and serve the UI:

php -S 127.0.0.1:8002 -t examples/private-chat/public

If you installed the package inside another project with Composer, run:

php vendor/micilini/php-websockets/examples/private-chat/server.php
php -S 127.0.0.1:8002 -t vendor/micilini/php-websockets/examples/private-chat/public

Open:

http://127.0.0.1:8002

PrivateChat demonstrates:

  • global room;
  • direct 1:1 conversations;
  • private group rooms;
  • selected participants;
  • unread badges;
  • attachments;
  • emoji picker;
  • message receipts;
  • bot commands.

Try the bot commands:

/help
/echo Hello PHPSockets

Example Matrix

Example Global Chat Direct 1:1 Private Groups Attachments Bots Best For
EasyChat First contact and learning
MediumChat Events and callbacks
PrivateChat Advanced realtime chat

Laravel Integration

PHPSockets can be used inside Laravel applications through Composer package discovery.

Install the package:

composer require micilini/php-websockets

Publish the configuration:

php artisan vendor:publish --tag=phpsockets-config

Check the package status:

php artisan phpsockets:status

Start the WebSocket chat server from Laravel:

php artisan phpsockets:serve

Run SQL migrations when using a persistent storage driver:

php artisan phpsockets:migrate --driver=sqlite

The package registers:

  • Micilini\PhpSockets\Laravel\PhpSocketsServiceProvider
  • Micilini\PhpSockets\Laravel\PhpSocketsFacade
  • phpsockets:serve
  • phpsockets:migrate
  • phpsockets:status

Example usage:

use Micilini\PhpSockets\Laravel\PhpSocketsFacade as PhpSockets;

PhpSockets::bots();

Laravel is optional. The native PHP core continues to work standalone.

Running PHPSockets in a Laravel Project

A Laravel app usually has two running processes:

Laravel HTTP server
  http://127.0.0.1:8000

PHPSockets WebSocket server
  ws://127.0.0.1:8080

Run the WebSocket server:

php artisan phpsockets:serve

In another terminal:

php artisan serve

Then your Laravel pages can connect to:

ws://127.0.0.1:8080

Hosting the Examples Through Laravel Routes

For a Laravel demo app, you can keep PHPSockets as the engine and let Laravel serve the example screens.

Recommended local package structure while developing PHPSockets inside a Laravel app:

laravel-app/
  packages/
    micilini/
      php-websockets/
  public/
    phpsockets/
      easy/
      medium/
      private/
  routes/
    web.php

After installing from Packagist, the package will live under:

laravel-app/
  vendor/
    micilini/
      php-websockets/

The idea is:

http://127.0.0.1:8000/
  Dashboard with Easy, Medium and Private options

http://127.0.0.1:8000/easy
  EasyChat hosted by Laravel

http://127.0.0.1:8000/medium
  MediumChat hosted by Laravel

http://127.0.0.1:8000/private
  PrivateChat hosted by Laravel

The WebSocket server still runs in a separate process, which is the correct model for realtime applications.

Configuration

After publishing the Laravel config, config/phpsockets.php contains:

return [
    'server' => [
        'host' => env('PHPSOCKETS_HOST', '127.0.0.1'),
        'port' => (int) env('PHPSOCKETS_PORT', 8080),
        'max_payload_bytes' => (int) env('PHPSOCKETS_MAX_PAYLOAD_BYTES', 4 * 1024 * 1024),
        'tick_microseconds' => (int) env('PHPSOCKETS_TICK_MICROSECONDS', 10000),
        'connection_limit' => (int) env('PHPSOCKETS_CONNECTION_LIMIT', 100),
        'debug' => (bool) env('PHPSOCKETS_DEBUG', false),
    ],

    'chat' => [
        'max_display_name_length' => (int) env('PHPSOCKETS_MAX_DISPLAY_NAME_LENGTH', 40),
        'max_room_name_length' => (int) env('PHPSOCKETS_MAX_ROOM_NAME_LENGTH', 80),
        'max_private_group_members' => (int) env('PHPSOCKETS_MAX_PRIVATE_GROUP_MEMBERS', 20),
        'history_limit' => (int) env('PHPSOCKETS_HISTORY_LIMIT', 50),
        'max_attachment_bytes' => (int) env('PHPSOCKETS_MAX_ATTACHMENT_BYTES', 2 * 1024 * 1024),
    ],

    'storage' => [
        'driver' => env('PHPSOCKETS_STORAGE', 'memory'),
        'database' => env('PHPSOCKETS_DATABASE'),
        'dsn' => env('PHPSOCKETS_DSN'),
        'username' => env('PHPSOCKETS_DB_USERNAME'),
        'password' => env('PHPSOCKETS_DB_PASSWORD'),
    ],
];

Example .env:

PHPSOCKETS_HOST=127.0.0.1
PHPSOCKETS_PORT=8080
PHPSOCKETS_MAX_PAYLOAD_BYTES=4194304
PHPSOCKETS_MAX_ATTACHMENT_BYTES=2097152
PHPSOCKETS_ATTACHMENT_DIR=.phpsockets/attachments
PHPSOCKETS_STORAGE=memory
PHPSOCKETS_DEBUG=true

API Overview

Main server

use Micilini\PhpSockets\Chat\ChatServer;
use Micilini\PhpSockets\Config\ChatConfig;
use Micilini\PhpSockets\Config\ServerConfig;

$server = ChatServer::create(
    ServerConfig::new(host: '127.0.0.1', port: 8080),
    ChatConfig::new(),
);

$server->run();

Server events

$server->on('user.joined', function (array $event): void {
    // User joined the chat.
});

$server->on('user.left', function (array $event): void {
    // User left the chat.
});

$server->on('message.received', function (array $event): void {
    // A chat message was received.
});

$server->on('room.created', function (array $event): void {
    // A private room was created.
});

$server->on('bot.responded', function (array $event): void {
    // A bot generated a response.
});

Available high-level events

  • user.joined
  • user.left
  • message.received
  • message.sent
  • room.created
  • bot.responded

Available low-level socket events

  • open
  • close
  • error

Storage

PHPSockets uses in-memory storage by default. This is perfect for examples, demos and development.

Available storage options:

memory
file JSONL messages
pdo sqlite
pdo mysql
pdo pgsql

SQLite migration example

use Micilini\PhpSockets\Database\MigrationRunner;
use Micilini\PhpSockets\Storage\Pdo\PdoConnectionFactory;

$pdo = PdoConnectionFactory::sqlite(__DIR__ . '/storage/phpsockets.sqlite');

(new MigrationRunner($pdo))->run('sqlite');

Laravel migration command

php artisan phpsockets:migrate --driver=sqlite --database=database/phpsockets.sqlite

Attachments

The examples support small file messages.

Attachment runtime directory

By default, PHPSockets stores temporary example attachments in a project-local directory:

.phpsockets/attachments

You can override this location with:

PHPSOCKETS_ATTACHMENT_DIR=/absolute/path/to/attachments

This is especially useful for production deployments, Laravel apps, containers and Windows environments.

Supported MIME types:

image/png
image/jpeg
image/gif
application/pdf
text/plain

Default max file size:

2 MB

Attachment behavior:

  • selecting a file does not send it immediately;
  • the selected file appears as a pending attachment;
  • the user can add a text caption;
  • the file is sent only when the user clicks Send;
  • delivered files include a download button;
  • unsafe MIME types are rejected;
  • files above the configured limit are rejected.

The initial transport uses JSON text-frame envelopes with base64 payloads over WebSocket. Larger uploads should use a future HTTP upload flow with WebSocket metadata messages.

Bot Hooks

PHPSockets includes a lightweight bot layer.

Bots can listen to text messages and return a response in the same conversation context.

Supported contexts:

  • Global Room;
  • Direct conversations;
  • Private group rooms.

Example:

use Micilini\PhpSockets\Contracts\BotInterface;
use Micilini\PhpSockets\Chat\Bot\BotContext;
use Micilini\PhpSockets\Chat\Bot\BotResponse;

final class EchoBot implements BotInterface
{
    public function name(): string
    {
        return 'EchoBot';
    }

    public function handle(BotContext $context): ?BotResponse
    {
        $text = trim($context->text());

        if (!str_starts_with($text, '/echo ')) {
            return null;
        }

        return BotResponse::text(substr($text, 6));
    }
}

Register the bot:

$server->bots()->register(new EchoBot());

Bots are intentionally simple in v1. They do not require any external AI API.

Protocol Notes

PHPSockets uses JSON text frames for chat messages.

Example client message:

{
  "type": "message.global",
  "payload": {
    "text": "Hello world 🚀"
  }
}

Example server message:

{
  "type": "message.received",
  "payload": {
    "message": {
      "id": "msg_01",
      "roomId": "global",
      "fromUserId": "usr_01",
      "kind": "text",
      "body": "Hello world 🚀"
    }
  }
}

Binary WebSocket frames are not accepted by the chat core in this version. File messages are transported as validated JSON text-frame envelopes.

Security Model

PHPSockets includes a practical safety baseline for examples and local-first applications:

  • messages are rendered safely in the examples;
  • user-provided text should not be rendered with unsafe innerHTML;
  • display names are normalized and must be unique while online;
  • payload size is limited;
  • attachment size is limited;
  • attachment MIME type is validated;
  • users cannot send messages to rooms they do not belong to;
  • private group messages are delivered only to room members;
  • bot messages do not trigger infinite bot loops;
  • malformed frames can be rejected with WebSocket close codes.

This package is a realtime foundation. Production applications should still add authentication, authorization, HTTPS/WSS, monitoring, rate limiting, persistent storage and infrastructure hardening.

Project Structure

src/
  WebSocket.php
  Config/
    ServerConfig.php
    ChatConfig.php
  Protocol/
    Handshake.php
    Frame.php
    FrameCodec.php
    Opcode.php
    CloseCode.php
  Server/
    WebSocketServer.php
    ServerRuntime.php
    SocketServer.php
    Loop.php
  Connection/
    Connection.php
    ConnectionRegistry.php
  Events/
    CallbackEventDispatcher.php
    EventDispatcher.php
  Contracts/
    BotInterface.php
    SessionStoreInterface.php
    MessageStoreInterface.php
    RoomStoreInterface.php
    AttachmentStoreInterface.php
  Chat/
    ChatServer.php
    ChatKernel.php
    ChatMessage.php
    Room.php
    RoomManager.php
    PresenceManager.php
    Bot/
      BotManager.php
      BotContext.php
      BotResponse.php
  Storage/
    InMemory/
    File/
    Pdo/
  Database/
    MigrationRunner.php
    Schema/
  Laravel/
    PhpSocketsServiceProvider.php
    PhpSocketsFacade.php
    PhpSocketsManager.php
    Commands/
      ServeCommand.php
      MigrateCommand.php
      StatusCommand.php

examples/
  easy-chat/
  medium-chat/
  private-chat/

legacy/
  EasyChat/
  MediumChat/

Testing and Quality

Install development dependencies:

composer install

Validate the package:

composer validate --strict

Run tests:

composer test

Run static analysis:

composer analyse

Check code style:

composer cs:check

Fix code style:

composer cs:fix

Run the full quality pipeline:

composer quality

Legacy Code

The original 2016 implementation is preserved under:

legacy/EasyChat
legacy/MediumChat
legacy/README-2016.md
legacy/NOTES.md

The legacy code is kept for historical and educational purposes only. The modern Composer package lives in src/ and does not depend on the old structure.

Roadmap

Current v1 focus:

  • native WebSocket core;
  • realtime server runtime;
  • chat kit;
  • EasyChat, MediumChat and PrivateChat;
  • private groups;
  • storage adapters;
  • small attachments;
  • bot hooks;
  • Laravel integration;
  • documentation and Packagist release.

Future ideas:

  • standalone CLI binary;
  • WSS/TLS helper documentation;
  • Redis adapter;
  • multi-process scaling;
  • chunked binary file transfer;
  • HTTP upload + WebSocket metadata;
  • JWT/auth adapters;
  • Laravel dashboard;
  • official Docker image;
  • benchmarks.

Packagist Release Checklist

Before tagging a stable release:

composer validate --strict
composer quality

Then:

git tag v1.0.0
git push origin v1.0.0

Submit the repository to Packagist:

https://packagist.org/packages/submit

After Packagist detects the package, users can install it with:

composer require micilini/php-websockets

Repository:

https://github.com/micilini/php-websockets

License

PHPSockets is open-sourced software licensed under the MIT license.

Credits

Created and maintained by Micilini.

Originally created in 2016 and rebuilt as a modern native PHP realtime foundation in 2026.