-
Add the event constant to src/Server/WebSocket/Events.php:
public const EVENT_NAME = 'event_name';
Use SCREAMING_SNAKE_CASE for the constant, snake_case for the wire value. Group related constants with a // section comment (e.g. // SyncPlay events).
Verify the file parses: php -l src/Server/WebSocket/Events.php.
-
Register the handler in src/Server/WebSocket/MessageHandler.php inside registerDefaultHandlers(). Use the constant from Step 1:
$this->on(WebSocketEvents::EVENT_NAME, function (Connection $conn, array $payload): void {
$userId = $conn->getUserId();
if ($userId === null) {
$conn->sendMessage('error', ['code' => 'unauthorized', 'message' => 'Login required']);
return;
}
if (!isset($payload['target_id'], $payload['value'])) {
$conn->sendMessage('error', ['code' => 'invalid_payload', 'message' => 'target_id and value required']);
return;
}
});
This step uses the constant from Step 1.
-
Pick the correct fan-out method for the response:
$conn->sendMessage($type, $data) — reply to the originating connection only
$this->sendToUser($userId, $type, $data) — every connection for one user (multi-device)
$this->sendToSession($sessionId, $type, $data) — every member of a SyncPlay session
$this->broadcast($type, $data) — every connected client (use sparingly; admin events only)
For SyncPlay/group events ALWAYS prefer sendToSession(). For user notifications ALWAYS prefer sendToUser().
-
Wrap state mutations (DB writes, session updates) in try/catch. On \Throwable, log via $this->logger->error(...) and reply with $conn->sendMessage('error', ['code' => 'internal', 'message' => 'Operation failed']). Do NOT leak exception messages to the client.
-
Add a unit test at tests/Unit/Server/WebSocket/MessageHandlerTest.php (or tests/Unit/Session/SyncPlay/... for syncplay events). Mock Connection and assert sendMessage is called with the expected type+payload. Pattern:
public function testEventNameRepliesWithStatus(): void
{
$conn = $this->createMock(Connection::class);
$conn->method('getUserId')->willReturn(42);
$conn->expects($this->once())
->method('sendMessage')
->with('status_updated', $this->arrayHasKey('target_id'));
$handler = new MessageHandler($this->deps);
$handler->dispatch($conn, WebSocketEvents::EVENT_NAME, ['target_id' => 1, 'value' => 'x']);
}
-
Update the client in public/assets/js/ (search for existing socket.on( calls — usually public/assets/js/websocket.js or public/assets/js/syncplay.js). Use the same wire string as the constant value:
socket.on('event_name', (payload) => { });
socket.send({ type: 'event_name', target_id: 1, value: 'x' });
-
Verify before claiming done:
php -l src/Server/WebSocket/Events.php src/Server/WebSocket/MessageHandler.php
./vendor/bin/phpunit tests/Unit/Server/WebSocket/ (or the specific test file)
- Grep for the new constant:
grep -r 'WebSocketEvents::EVENT_NAME' src/ tests/ — must appear in both registration and tests.