Native PHP WebSocket and realtime chat foundation built from scratch with PHP sockets. Works standalone with plain PHP or inside Laravel applications.
Pure PHP WebSockets. Realtime chat. Private rooms. Bots. Laravel-ready.
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.
- 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.
Install through Composer:
composer require micilini/php-websocketsFor local development:
git clone https://github.com/micilini/php-websockets.git
cd php-websockets
composer installRequired:
- PHP 8.2 or higher;
ext-sockets;ext-json;- Composer.
Optional:
ext-pdofor SQL storage adapters;ext-pdo_sqlitefor SQLite storage and tests;- Laravel 10, 11, 12 or 13 for Laravel integration.
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.phpThe WebSocket server will listen on:
ws://127.0.0.1:8080The 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.phpto locate the correct Composer autoload file automatically.
Each example has two parts:
- a WebSocket server process;
- a browser UI served by PHP's built-in HTTP server.
Start the WebSocket server:
php examples/easy-chat/server.phpOpen another terminal and serve the UI:
php -S 127.0.0.1:8000 -t examples/easy-chat/publicIf 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/publicOpen:
http://127.0.0.1:8000EasyChat demonstrates:
- global chat;
- unique display names;
- presence;
- typing indicators;
- emoji picker;
- small attachments;
- safe message rendering.
Start the WebSocket server:
php examples/medium-chat/server.phpOpen another terminal and serve the UI:
php -S 127.0.0.1:8001 -t examples/medium-chat/publicIf 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/publicOpen:
http://127.0.0.1:8001MediumChat 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.
Start the WebSocket server:
php examples/private-chat/server.phpOpen another terminal and serve the UI:
php -S 127.0.0.1:8002 -t examples/private-chat/publicIf 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/publicOpen:
http://127.0.0.1:8002PrivateChat 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 | Global Chat | Direct 1:1 | Private Groups | Attachments | Bots | Best For |
|---|---|---|---|---|---|---|
| EasyChat | β | β | β | β | β | First contact and learning |
| MediumChat | β | β | β | β | β | Events and callbacks |
| PrivateChat | β | β | β | β | β | Advanced realtime chat |
PHPSockets can be used inside Laravel applications through Composer package discovery.
Install the package:
composer require micilini/php-websocketsPublish the configuration:
php artisan vendor:publish --tag=phpsockets-configCheck the package status:
php artisan phpsockets:statusStart the WebSocket chat server from Laravel:
php artisan phpsockets:serveRun SQL migrations when using a persistent storage driver:
php artisan phpsockets:migrate --driver=sqliteThe package registers:
Micilini\PhpSockets\Laravel\PhpSocketsServiceProviderMicilini\PhpSockets\Laravel\PhpSocketsFacadephpsockets:servephpsockets:migratephpsockets:status
Example usage:
use Micilini\PhpSockets\Laravel\PhpSocketsFacade as PhpSockets;
PhpSockets::bots();Laravel is optional. The native PHP core continues to work standalone.
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:8080Run the WebSocket server:
php artisan phpsockets:serveIn another terminal:
php artisan serveThen your Laravel pages can connect to:
ws://127.0.0.1:8080For 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.phpAfter 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 LaravelThe WebSocket server still runs in a separate process, which is the correct model for realtime applications.
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=trueuse 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->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.
});user.joineduser.leftmessage.receivedmessage.sentroom.createdbot.responded
opencloseerror
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 pgsqluse Micilini\PhpSockets\Database\MigrationRunner;
use Micilini\PhpSockets\Storage\Pdo\PdoConnectionFactory;
$pdo = PdoConnectionFactory::sqlite(__DIR__ . '/storage/phpsockets.sqlite');
(new MigrationRunner($pdo))->run('sqlite');php artisan phpsockets:migrate --driver=sqlite --database=database/phpsockets.sqliteThe examples support small file messages.
By default, PHPSockets stores temporary example attachments in a project-local directory:
.phpsockets/attachmentsYou can override this location with:
PHPSOCKETS_ATTACHMENT_DIR=/absolute/path/to/attachmentsThis is especially useful for production deployments, Laravel apps, containers and Windows environments.
Supported MIME types:
image/png
image/jpeg
image/gif
application/pdf
text/plainDefault max file size:
2 MBAttachment 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.
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.
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.
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.
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/Install development dependencies:
composer installValidate the package:
composer validate --strictRun tests:
composer testRun static analysis:
composer analyseCheck code style:
composer cs:checkFix code style:
composer cs:fixRun the full quality pipeline:
composer qualityThe original 2016 implementation is preserved under:
legacy/EasyChat
legacy/MediumChat
legacy/README-2016.md
legacy/NOTES.mdThe 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.
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.
Before tagging a stable release:
composer validate --strict
composer qualityThen:
git tag v1.0.0
git push origin v1.0.0Submit the repository to Packagist:
https://packagist.org/packages/submitAfter Packagist detects the package, users can install it with:
composer require micilini/php-websocketsRepository:
https://github.com/micilini/php-websocketsPHPSockets is open-sourced software licensed under the MIT license.
Created and maintained by Micilini.
Originally created in 2016 and rebuilt as a modern native PHP realtime foundation in 2026.



