Controller API Reference
The SocketController
class is the base class for all controllers in Sockeon, providing access to server functionality and convenience methods for WebSocket and HTTP handling.
Class: Sockeon\Sockeon\Controllers\SocketController
Abstract Base Class
All controllers must extend this abstract class:
use Sockeon\Sockeon\Controllers\SocketController;
class MyController extends SocketController
{
// Your controller methods here
}
Core Methods
setServer()
public function setServer(Server $server): void
Sets the server instance for this controller. This method is called automatically by the server when registering the controller.
Parameters:$server
(Server
): The server instance
WebSocket Communication
emit()
public function emit(int $clientId, string $event, array $data): void
Sends an event to a specific client.
Parameters:$clientId
(int
): The client ID to send to$event
(string
): The event name$data
(array<string, mixed>
): The data to send
#[SocketOn('user.request')]
public function handleUserRequest(int $clientId, array $data): void
{
$this->emit($clientId, 'user.response', [
'message' => 'Request processed',
'timestamp' => time()
]);
}
broadcast()
public function broadcast(string $event, array $data, ?string $namespace = null, ?string $room = null): void
Broadcasts an event to clients. Can broadcast to all clients, clients in a namespace, or clients in a specific room.
Parameters:$event
(string
): The event name$data
(array<string, mixed>
): The data to broadcast$namespace
(string|null
): Optional namespace to broadcast within$room
(string|null
): Optional room to broadcast to
#[SocketOn('chat.message')]
public function handleChatMessage(int $clientId, array $data): void
{
// Broadcast to all clients
$this->broadcast('chat.message', [
'from' => $clientId,
'message' => $data['message'],
'timestamp' => time()
]);
// Or broadcast to specific namespace
$this->broadcast('chat.message', [
'from' => $clientId,
'message' => $data['message'],
'timestamp' => time()
], '/game');
// Or broadcast to specific room
$this->broadcast('chat.message', [
'from' => $clientId,
'message' => $data['message'],
'timestamp' => time()
], '/game', 'room1');
}
broadcastToRoomClients()
public function broadcastToRoomClients(string $event, array $data, string $room, string $namespace = '/'): void
Broadcasts an event to all clients in a specific room within a namespace.
Parameters:$event
(string
): The event name$data
(array<string, mixed>
): The data to broadcast$room
(string
): The room name$namespace
(string
): The namespace (default: '/')
#[SocketOn('room.message')]
public function handleRoomMessage(int $clientId, array $data): void
{
$room = $data['room'] ?? 'general';
$this->broadcastToRoomClients('room.message', [
'from' => $clientId,
'message' => $data['message'],
'room' => $room
], $room, '/chat');
}
broadcastToNamespaceClients()
public function broadcastToNamespaceClients(string $event, array $data, string $namespace): void
Broadcasts an event to all clients in a specific namespace.
Parameters:$event
(string
): The event name$data
(array<string, mixed>
): The data to broadcast$namespace
(string
): The namespace
#[HttpRoute('POST', '/api/admin/announcement')]
public function sendAnnouncement(Request $request): Response
{
$data = $request->all();
$this->broadcastToNamespaceClients('announcement', [
'message' => $data['message'],
'priority' => $data['priority'] ?? 'normal',
'timestamp' => time()
], '/admin');
return Response::json(['success' => true]);
}
Room and Namespace Management
joinRoom()
public function joinRoom(int $clientId, string $room, string $namespace = '/'): void
Adds a client to a room within a namespace.
Parameters:$clientId
(int
): The client ID$room
(string
): The room name$namespace
(string
): The namespace (default: '/')
#[SocketOn('chat.join')]
public function joinChatRoom(int $clientId, array $data): void
{
$room = $data['room'] ?? 'general';
$this->joinRoom($clientId, $room, '/chat');
$this->emit($clientId, 'room.joined', [
'room' => $room,
'message' => "You joined {$room}"
]);
$this->broadcastToRoomClients('user.joined', [
'clientId' => $clientId
], $room, '/chat');
}
leaveRoom()
public function leaveRoom(int $clientId, string $room, string $namespace = '/'): void
Removes a client from a room within a namespace.
Parameters:$clientId
(int
): The client ID$room
(string
): The room name$namespace
(string
): The namespace (default: '/')
#[SocketOn('chat.leave')]
public function leaveChatRoom(int $clientId, array $data): void
{
$room = $data['room'] ?? 'general';
$this->leaveRoom($clientId, $room, '/chat');
$this->broadcastToRoom($room, 'user.left', [
'clientId' => $clientId
], '/chat');
$this->emit($clientId, 'room.left', [
'room' => $room
]);
}
moveClientToNamespace()
public function moveClientToNamespace(int $clientId, string $namespace = '/'): void
Moves a client to a namespace.
Parameters:$clientId
(int
): The client ID$namespace
(string
): The namespace (default: '/')
#[OnConnect]
public function onConnect(int $clientId): void
{
// Move client to chat namespace
$this->moveClientToNamespace($clientId, '/chat');
$this->emit($clientId, 'namespace.joined', [
'namespace' => '/chat',
'welcome' => 'Welcome to chat!'
]);
}
leaveNamespace()
public function leaveNamespace(int $clientId): void
Removes a client from their current namespace.
Parameters:$clientId
(int
): The client ID
#[SocketOn('namespace.switch')]
public function switchNamespace(int $clientId, array $data): void
{
$newNamespace = $data['namespace'] ?? '/';
// Move to new namespace
$this->moveClientToNamespace($clientId, $newNamespace);
$this->emit($clientId, 'namespace.switched', [
'namespace' => $newNamespace
]);
}
Server Access
getServer()
protected function getServer(): Server
Returns the server instance for direct access to server methods.
Returns:Server
- The server instance
Example:
#[HttpRoute('GET', '/api/status')]
public function getStatus(Request $request): Response
{
$server = $this->getServer();
return Response::json([
'clients_connected' => $server->getClientCount(),
'client_types' => $server->getClientTypes(),
'rate_limiting_enabled' => $server->isRateLimitingEnabled()
]);
}
#[SocketOn('server.info')]
public function getServerInfo(int $clientId, array $data): void
{
$server = $this->getServer();
$this->emit($clientId, 'server.info', [
'total_clients' => $server->getClientCount(),
'your_id' => $clientId,
'your_type' => $server->getClientType($clientId)
]);
}
Attribute-Based Routing
WebSocket Event Attributes
#[OnConnect]
Marks a method to be called when a client connects.
#[OnConnect]
public function onConnect(int $clientId): void
{
$this->emit($clientId, 'welcome', [
'message' => 'Welcome to the server!',
'clientId' => $clientId
]);
}
#[OnDisconnect]
Marks a method to be called when a client disconnects.
#[OnDisconnect]
public function onDisconnect(int $clientId): void
{
$this->broadcast('user.left', [
'clientId' => $clientId,
'message' => "User {$clientId} has left"
]);
}
#[SocketOn('event.name')]
Marks a method to handle a specific WebSocket event.
#[SocketOn('chat.message')]
public function handleChatMessage(int $clientId, array $data): void
{
// Handle the chat message event
}
#[SocketOn('user.typing')]
public function handleTyping(int $clientId, array $data): void
{
// Handle typing indicator
}
#[SocketOn('admin.command', middlewares: [AdminMiddleware::class])]
public function handleAdminCommand(int $clientId, array $data): void
{
// Only admins can execute this
}
#[SocketOn('public.event', excludeGlobalMiddlewares: [AuthMiddleware::class])]
public function handlePublicEvent(int $clientId, array $data): void
{
// Public event - no auth required
}
HTTP Route Attributes
#[HttpRoute('METHOD', '/path')]
Marks a method to handle HTTP requests.
#[HttpRoute('GET', '/api/users')]
public function listUsers(Request $request): Response
{
return Response::json(['users' => []]);
}
#[HttpRoute('POST', '/api/users')]
public function createUser(Request $request): Response
{
$data = $request->all();
// Create user logic
return Response::json(['success' => true], 201);
}
#[HttpRoute('GET', '/api/users/{id}')]
public function getUser(Request $request): Response
{
$userId = $request->getParam('id');
// Get user logic
return Response::json(['user' => ['id' => $userId]]);
}
#[HttpRoute('GET', '/api/admin/users', middlewares: [AuthMiddleware::class, AdminMiddleware::class])]
public function adminListUsers(Request $request): Response
{
// Requires auth and admin role
}
#[HttpRoute('GET', '/api/public/health', excludeGlobalMiddlewares: [AuthMiddleware::class])]
public function healthCheck(Request $request): Response
{
// Public endpoint - no auth
return Response::json(['status' => 'healthy']);
}
Rate Limiting Attribute
#[RateLimit(maxCount, timeWindow)]
Applies rate limiting to a specific method.
#[SocketOn('chat.message')]
#[RateLimit(maxCount: 10, timeWindow: 60)] // 10 messages per minute
public function handleChatMessage(int $clientId, array $data): void
{
// Rate limited chat messages
}
#[HttpRoute('POST', '/api/upload')]
#[RateLimit(maxCount: 5, timeWindow: 300)] // 5 uploads per 5 minutes
public function uploadFile(Request $request): Response
{
// Rate limited file uploads
}
Complete Controller Examples
Chat Controller
<?php
use Sockeon\Sockeon\Controllers\SocketController;
use Sockeon\Sockeon\Http\Attributes\HttpRoute;
use Sockeon\Sockeon\Http\Request;
use Sockeon\Sockeon\Http\Response;
use Sockeon\Sockeon\WebSocket\Attributes\OnConnect;
use Sockeon\Sockeon\WebSocket\Attributes\OnDisconnect;
use Sockeon\Sockeon\WebSocket\Attributes\SocketOn;
class ChatController extends SocketController
{
private array $userRooms = [];
#[OnConnect]
public function onConnect(int $clientId): void
{
// Add to chat namespace and default room
$this->moveClientToNamespace($clientId, '/chat');
$this->joinRoom($clientId, 'general', '/chat');
$this->userRooms[$clientId] = 'general';
$this->emit($clientId, 'welcome', [
'message' => 'Welcome to chat!',
'room' => 'general'
]);
$this->broadcastToRoom('general', 'user.joined', [
'clientId' => $clientId
], '/chat');
}
#[OnDisconnect]
public function onDisconnect(int $clientId): void
{
$room = $this->userRooms[$clientId] ?? 'general';
$this->broadcastToRoom($room, 'user.left', [
'clientId' => $clientId
], '/chat');
unset($this->userRooms[$clientId]);
}
#[SocketOn('chat.message')]
public function handleMessage(int $clientId, array $data): void
{
$message = $data['message'] ?? '';
$room = $this->userRooms[$clientId] ?? 'general';
if (empty($message)) {
$this->emit($clientId, 'error', ['message' => 'Message cannot be empty']);
return;
}
$this->broadcastToRoom($room, 'chat.message', [
'from' => $clientId,
'message' => $message,
'timestamp' => time()
], '/chat');
}
#[SocketOn('room.switch')]
public function switchRoom(int $clientId, array $data): void
{
$newRoom = $data['room'] ?? 'general';
$oldRoom = $this->userRooms[$clientId] ?? 'general';
// Leave old room
$this->leaveRoom($clientId, $oldRoom, '/chat');
$this->broadcastToRoom($oldRoom, 'user.left', ['clientId' => $clientId], '/chat');
// Join new room
$this->joinRoom($clientId, $newRoom, '/chat');
$this->userRooms[$clientId] = $newRoom;
$this->emit($clientId, 'room.switched', ['room' => $newRoom]);
$this->broadcastToRoom($newRoom, 'user.joined', ['clientId' => $clientId], '/chat');
}
#[HttpRoute('GET', '/api/chat/rooms')]
public function listRooms(Request $request): Response
{
$rooms = [];
foreach ($this->userRooms as $clientId => $room) {
if (!isset($rooms[$room])) {
$rooms[$room] = 0;
}
$rooms[$room]++;
}
return Response::json(['rooms' => $rooms]);
}
#[HttpRoute('POST', '/api/chat/broadcast')]
public function broadcastMessage(Request $request): Response
{
$data = $request->all();
$message = $data['message'] ?? '';
$room = $data['room'] ?? null;
if (empty($message)) {
return Response::json(['error' => 'Message required'], 400);
}
if ($room) {
$this->broadcastToRoom($room, 'admin.message', [
'message' => $message,
'from' => 'admin'
], '/chat');
} else {
$this->broadcastToNamespace('/chat', 'admin.message', [
'message' => $message,
'from' => 'admin'
]);
}
return Response::json(['success' => true]);
}
}
Game Controller
<?php
class GameController extends SocketController
{
private array $games = [];
private array $playerGames = [];
#[OnConnect]
public function onConnect(int $clientId): void
{
$this->moveClientToNamespace($clientId, '/game');
$this->joinRoom($clientId, 'lobby', '/game');
$this->emit($clientId, 'lobby.joined', [
'message' => 'Welcome to the game lobby!'
]);
}
#[SocketOn('game.create')]
public function createGame(int $clientId, array $data): void
{
$gameId = uniqid('game_');
$this->games[$gameId] = [
'id' => $gameId,
'host' => $clientId,
'players' => [$clientId],
'status' => 'waiting',
'created' => time()
];
$this->playerGames[$clientId] = $gameId;
// Move from lobby to game room
$this->leaveRoom($clientId, 'lobby', '/game');
$this->joinRoom($clientId, $gameId, '/game');
$this->emit($clientId, 'game.created', [
'gameId' => $gameId,
'role' => 'host'
]);
// Notify lobby
$this->broadcastToRoom('lobby', 'game.available', [
'gameId' => $gameId,
'host' => $clientId
], '/game');
}
#[SocketOn('game.join')]
public function joinGame(int $clientId, array $data): void
{
$gameId = $data['gameId'] ?? null;
if (!isset($this->games[$gameId])) {
$this->emit($clientId, 'error', ['message' => 'Game not found']);
return;
}
$game = &$this->games[$gameId];
if (count($game['players']) >= 2) {
$this->emit($clientId, 'error', ['message' => 'Game is full']);
return;
}
// Add player to game
$game['players'][] = $clientId;
$this->playerGames[$clientId] = $gameId;
// Move from lobby to game room
$this->leaveRoom($clientId, 'lobby', '/game');
$this->joinRoom($clientId, $gameId, '/game');
// Notify game players
$this->broadcastToRoom($gameId, 'player.joined', [
'playerId' => $clientId,
'playerCount' => count($game['players'])
], '/game');
// Start game if full
if (count($game['players']) >= 2) {
$game['status'] = 'playing';
$this->broadcastToRoom($gameId, 'game.started', [
'players' => $game['players']
], '/game');
}
}
#[SocketOn('game.move')]
public function makeMove(int $clientId, array $data): void
{
$gameId = $this->playerGames[$clientId] ?? null;
if (!$gameId || !isset($this->games[$gameId])) {
$this->emit($clientId, 'error', ['message' => 'Not in a game']);
return;
}
$move = $data['move'] ?? null;
// Broadcast move to other players
$this->broadcastToRoom($gameId, 'game.move', [
'player' => $clientId,
'move' => $move,
'timestamp' => time()
], '/game');
}
#[OnDisconnect]
public function onDisconnect(int $clientId): void
{
$gameId = $this->playerGames[$clientId] ?? null;
if ($gameId && isset($this->games[$gameId])) {
// Notify other players
$this->broadcastToRoom($gameId, 'player.disconnected', [
'playerId' => $clientId
], '/game');
// Remove game if host left
if ($this->games[$gameId]['host'] === $clientId) {
unset($this->games[$gameId]);
}
}
unset($this->playerGames[$clientId]);
}
#[HttpRoute('GET', '/api/games')]
public function listGames(Request $request): Response
{
$gameList = array_map(function($game) {
return [
'id' => $game['id'],
'host' => $game['host'],
'players' => count($game['players']),
'status' => $game['status'],
'created' => $game['created']
];
}, $this->games);
return Response::json(['games' => array_values($gameList)]);
}
}
Best Practices
- Use Type Hints: Always specify parameter and return types
- Validate Input: Check all incoming data before processing
- Handle Errors Gracefully: Send appropriate error responses
- Keep Methods Focused: Each method should handle one specific task
- Use Meaningful Names: Choose descriptive method and event names
- Document Complex Logic: Use PHPDoc for complex methods
- Clean Up Resources: Always clean up in disconnect handlers
See Also
- Server API - Server instance methods
- Request API - HTTP request handling
- Response API - HTTP response creation
- Routing Guide - Advanced routing patterns