OddSockets PHP SDK
Official PHP SDK for OddSockets real-time messaging platform
Overview & Features
The OddSockets PHP SDK provides a powerful, event-driven interface for real-time messaging in PHP applications, built on ReactPHP for high-performance async operations.
Modern PHP
Built for PHP 8.1+ with strict typing, union types, and modern language features.
Event-Driven
ReactPHP-based async architecture with EventEmitter pattern and Promise support.
JS Compatible
Identical API to JavaScript SDK for consistent cross-platform development.
High Performance
Optimized WebSocket connections with automatic reconnection and load balancing.
Cost Effective
No per-message pricing, industry-standard 32KB message limits, transparent pricing.
Automatic Failover
Built-in redundancy and intelligent error handling for 99.9% uptime.
Installation
composer require jyswee/oddsockets-php-sdk
{
"require": {
"php": "^8.1",
"ext-json": "*",
"ext-curl": "*",
"guzzlehttp/guzzle": "^7.8",
"ratchet/pawl": "^0.4",
"react/promise": "^3.0",
"evenement/evenement": "^3.0"
}
}
Quick Start
Basic Usage
<?php
require_once 'vendor/autoload.php';
use OddSockets\OddSockets;
use React\EventLoop\Loop;
// Create client
$client = OddSockets::create('ak_live_1234567890abcdef');
// Get channel
$channel = $client->channel('my-channel');
// Subscribe to messages
$channel->subscribe(function ($message) {
echo "Received: " . json_encode($message) . "\n";
});
// Publish a message
$channel->publish('Hello, World!');
// Start the event loop
Loop::get()->run();
Advanced Configuration
<?php
use OddSockets\Config\OddSocketsConfig;
$config = OddSocketsConfig::builder('ak_live_1234567890abcdef')
->userId('user123')
->autoConnect(false)
->reconnectAttempts(3)
->timeout(15)
->build();
$client = OddSockets::create($config);
$client->on('connected', function () {
echo "Connected to OddSockets!\n";
});
$client->connect();
Promise-Based Operations
<?php
$channel = $client->channel('async-channel');
$channel->subscribe($callback)
->then(function () {
echo "Successfully subscribed\n";
return $channel->publish(['message' => 'Hello Async!']);
})
->then(function ($result) {
echo "Message published: " . json_encode($result) . "\n";
})
->otherwise(function ($error) {
echo "Error: " . $error->getMessage() . "\n";
});
Configuration
Client Options
$client = OddSockets::create([
'apiKey' => 'your-api-key', // Required: Your OddSockets API key
'userId' => 'user-id', // Optional: User identifier
'autoConnect' => true, // Optional: Auto-connect on creation
'reconnectAttempts' => 5, // Optional: Max reconnection attempts
'heartbeatInterval' => 30000 // Optional: Heartbeat interval (ms)
]);
Channel Options
$channel->subscribe($callback, [
'enablePresence' => true, // Enable presence tracking
'retainHistory' => true, // Retain message history
'maxHistory' => 100 // Maximum history size
]);
$channel->publish($message, [
'ttl' => 3600, // Time to live (seconds)
'metadata' => ['priority' => 'high'], // Additional metadata
'storeInHistory' => true // Store in message history
]);
Examples
Explore comprehensive examples demonstrating the OddSockets PHP SDK in action:
Enhanced Features
Beyond core pub/sub, OddSockets ships a Slack-like enhanced surface: typing indicators, reactions, threads, read receipts, presence/status, notifications, DMs, channel management, message editing and search. It lives on the public $client->enhanced property. Send actions with $client->enhanced->* (camelCase) and receive the paired broadcasts with $client->on('<event>', $handler).
Typing & Reactions
<?php
use OddSockets\OddSockets;
use React\EventLoop\Loop;
$client = OddSockets::create(['apiKey' => 'ak_live_1234567890abcdef', 'userId' => 'alice']);
$client->connect()->then(function () use ($client) {
$channel = $client->channel('room-42');
$channel->subscribe(function ($msg) {}, ['enablePresence' => true]);
// Receive-path: broadcasts from other users on the channel
$client->on('user_typing', fn($e) => print("{$e['userId']} is typing\n"));
$client->on('reaction_added', fn($e) => print("{$e['userId']} reacted {$e['emoji']}\n"));
// Send-path: enhanced actions over the live socket
$client->enhanced->startTyping('alice', 'room-42');
$client->enhanced->addReaction('msg-1', 'room-42', ':thumbsup:', 'alice', 'Alice');
});
Loop::get()->run();
Threads
$client->on('thread_reply', fn($e) => print("New reply\n"));
$client->enhanced->threadReply('room-42', 'msg-1', 'Replying in the thread', 'alice', 'Alice');
// Query methods return ReactPHP promises
$client->enhanced->getThread('thread-1')->then(fn($thread) => var_dump($thread));
Enhanced surface
Each area exposes methods on $client->enhanced; the worker broadcasts the paired events which you handle with $client->on(...). Query methods (get*, search*) return promises that resolve with the worker response.
- Typing —
startTyping,stopTyping→user_typing,user_stopped_typing - Reactions —
addReaction,removeReaction,getReactions→reaction_added,reaction_removed - Threads —
threadReply,getThread,subscribeThread,followThread,markThreadRead→thread_reply,thread_subscribed,thread_followed,thread_read_updated - Read receipts —
markRead,markAllRead,getUnreadCounts→user_read,unread_count_updated,all_marked_read - Messages —
editMessage,deleteMessage,pinMessage,unpinMessage,getPinnedMessages,searchMessages→message_edited,message_deleted,message_pinned,message_unpinned - Presence & status —
setStatus,setCustomStatus,setDND,getUserPresence→user_status_changed,custom_status_updated,dnd_status_changed - Channels —
createChannel,updateChannel,archiveChannel,inviteToChannel,joinChannel,leaveChannel→channel_created,channel_updated,user_invited,user_joined_channel,user_left_channel - DMs —
createDM,sendDM,getDMConversations→dm_created,dm_received - Notifications —
subscribeNotifications,getNotifications,markNotificationRead,clearNotifications→notification,notification_read,notifications_cleared
For any worker event not wrapped above, subscribe with the raw $client->on('<event>', $handler) API — all enhanced broadcasts are forwarded onto the client surface.
Challenges & Leaderboards
Challenges, leaderboards and achievements build on the same live socket. The send side lives on the enhanced surface ($client->enhanced->*); request/query methods return a ReactPHP PromiseInterface that resolves with the worker's reply (->then(...)), while progress and achievement calls are fire-and-forget. Inbound broadcasts arrive on the client event surface — subscribe with the client's normal $client->on('<event>', $handler).
$client->enhanced->createChallenge(['challengeId' => 'daily-sprint', 'metric' => 'points', 'ranked' => true]);
$client->enhanced->reportProgress(['challengeId' => 'daily-sprint', 'value' => 120]);
$client->enhanced->getStandings(['challengeId' => 'daily-sprint', 'limit' => 10])
->then(fn($board) => var_dump($board['standings'], $board['yourRank']));
$client->enhanced->completeChallenge(['challengeId' => 'daily-sprint', 'outcome' => 'completed']);
Methods
Request/query methods return a promise that resolves with the worker ack; progress and achievement calls are fire-and-forget (no ack).
createChallenge— create a challenge/leaderboard → ackchallenge_create_successreportProgress— fire-and-forget metric progress (no ack)completeChallenge— finalize with anoutcome→ ackchallenge_complete_successunlockAchievement— fire-and-forget; passpercentComplete(0–100) (no ack)getStandings— request top-N + caller rank → ackchallenge_standings_successgetAchievements— query achievement state → ackachievement_statesendChallengeInvite— directed invite to another user → ackchallenge_invite_successreplyChallengeInvite— accept/decline an invite → ackchallenge_reply_successcancelChallengeInvite— cancel a sent invite → ackchallenge_invite_cancel_successgetChallengeInvites— list pending invites → ackchallenge_invites
Completion outcomes
The outcome passed to completeChallenge is one of:
completed— win (rank 1)failed— losstied— drawconceded— resign / concedeexpired— timed out
Progressive achievements
unlockAchievement always emits the wire event achievement_unlock; the worker is authoritative and derives the outbound broadcast from percentComplete. A value below 100 broadcasts achievement_progress (status in_progress); a value of 100 or omitted broadcasts achievement_unlock (status unlocked). You never emit achievement_progress yourself.
Inbound events
Subscribe with the client's normal $client->on('<event>', $handler).
- Room broadcasts —
challenge_progress,leaderboard_rank_change,challenge_complete,achievement_unlock,achievement_progress - Directed (per-user) —
challenge_invited,challenge_reply_received,challenge_invite_cancelled
Usage Analytics
Pull your tenant's headline usage tiles — monthly active users, daily active users, total messages published, and error rate — straight from the SDK, without hand-rolling a REST call. getUsageStats() resolves the same four tiles the developer dashboard renders.
$stats = $client->getUsageStats()->wait(); // returns a UsageStats
// Each accessor is int|float|null — null means "no data", so render an em-dash.
$tile = fn ($value) => $value === null ? "\u{2014}" : $value;
echo "MAU: " . $tile($stats->getMau()) . PHP_EOL;
echo "DAU: " . $tile($stats->getDau()) . PHP_EOL;
echo "Messages: " . $tile($stats->getTotalMessages()) . PHP_EOL;
echo "Errors: " . $tile($stats->getErrorRate()) . PHP_EOL;
The four tiles
- getMau() — monthly active users for your owner scope
- getDau() — daily active users
- getTotalMessages() — total messages published
- getErrorRate() — publish error rate,
0–1
Honesty rule — null, never a fake zero
Each tile is a number or null. A null means that leg of the analytics pipeline is not live yet for your tenant — the SDK returns null verbatim and never coerces it to 0. Render an em-dash (—) for a null tile so you never show a fabricated zero.
Requires an API key
getUsageStats() reads your owner-scoped analytics, so it needs an apiKey. Keyless / token-only clients have no owner scope to query and the promise will reject with getUsageStats requires an apiKey.
Performance & Compatibility
OddSockets PHP SDK delivers superior performance with broad compatibility:
PHP Support
- PHP 8.1+ (Latest)
- ReactPHP Ecosystem
- Composer Package
- PSR-4 Autoloading
Framework Support
- Laravel Integration
- Symfony Compatible
- Standalone Usage
- ReactPHP Apps
Framework Integrations
The OddSockets PHP SDK works seamlessly with popular PHP frameworks. Here are examples showing how to integrate:
Laravel
<?php
namespace App\Services;
use OddSockets\OddSockets;
use React\EventLoop\Loop;
class OddSocketsService
{
private $client;
public function __construct()
{
$this->client = OddSockets::create(config('oddsockets.api_key'));
$this->client->on('connected', function () {
\Log::info('OddSockets connected');
});
}
public function publishToChannel(string $channel, $message)
{
$channel = $this->client->channel($channel);
return $channel->publish($message)
->then(function ($result) {
\Log::info('Message published', $result);
return $result;
})
->otherwise(function ($error) {
\Log::error('Publish failed', ['error' => $error->getMessage()]);
throw $error;
});
}
public function startEventLoop()
{
Loop::get()->run();
}
}
Symfony
<?php
namespace App\Service;
use OddSockets\OddSockets;
use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
class OddSocketsService
{
private $client;
private LoggerInterface $logger;
public function __construct(
#[Autowire('%env(ODDSOCKETS_API_KEY)%')] string $apiKey,
LoggerInterface $logger
) {
$this->logger = $logger;
$this->client = OddSockets::create($apiKey);
$this->client->on('connected', function () {
$this->logger->info('OddSockets connected');
});
$this->client->on('error', function ($error) {
$this->logger->error('OddSockets error', ['error' => $error->getMessage()]);
});
}
public function subscribe(string $channelName, callable $callback): void
{
$channel = $this->client->channel($channelName);
$channel->subscribe($callback)
->then(function () use ($channelName) {
$this->logger->info('Subscribed to channel', ['channel' => $channelName]);
})
->otherwise(function ($error) use ($channelName) {
$this->logger->error('Subscription failed', [
'channel' => $channelName,
'error' => $error->getMessage()
]);
});
}
}
ReactPHP Application
<?php
require_once 'vendor/autoload.php';
use OddSockets\OddSockets;
use React\EventLoop\Loop;
use React\Http\HttpServer;
use React\Http\Message\Response;
use React\Socket\SocketServer;
$loop = Loop::get();
// Create OddSockets client
$oddSockets = OddSockets::create('ak_live_1234567890abcdef', $loop);
// Create HTTP server
$server = new HttpServer($loop, function ($request) use ($oddSockets) {
$channel = $oddSockets->channel('web-events');
// Publish web request event
$channel->publish([
'type' => 'http_request',
'method' => $request->getMethod(),
'uri' => $request->getUri()->getPath(),
'timestamp' => time()
]);
return new Response(200, [], 'Hello from ReactPHP + OddSockets!');
});
// Subscribe to events
$eventChannel = $oddSockets->channel('system-events');
$eventChannel->subscribe(function ($message) {
echo "System event: " . json_encode($message) . "\n";
});
// Start servers
$socket = new SocketServer('127.0.0.1:8080', [], $loop);
$server->listen($socket);
echo "Server running on http://127.0.0.1:8080\n";
echo "OddSockets integration active\n";
$loop->run();