{"record":{"id":"324f360ae1a5ab3a","repo":"ratchetphp/Ratchet","slug":"invalid-topic-must-be-a-string","errorCode":null,"errorMessage":"Invalid Topic, must be a string","messagePattern":"Invalid Topic, must be a string","errorType":"exception","errorClass":"Exception","httpStatus":null,"severity":"error","filePath":"src/Ratchet/Wamp/ServerProtocol.php","lineNumber":97,"sourceCode":"\n    /**\n     * {@inheritdoc}\n     * @throws \\Ratchet\\Wamp\\Exception\n     * @throws \\Ratchet\\Wamp\\JsonException\n     */\n    public function onMessage(ConnectionInterface $from, $msg) {\n        $from = $this->connections[$from];\n\n        if (null === ($json = @json_decode($msg, true))) {\n            throw new JsonException;\n        }\n\n        if (!is_array($json) || $json !== array_values($json)) {\n            throw new Exception(\"Invalid WAMP message format\");\n        }\n\n        if (isset($json[1]) && !(is_string($json[1]) || is_numeric($json[1]))) {\n            throw new Exception('Invalid Topic, must be a string');\n        }\n\n        switch ($json[0]) {\n            case static::MSG_PREFIX:\n                $from->WAMP->prefixes[$json[1]] = $json[2];\n            break;\n\n            case static::MSG_CALL:\n                array_shift($json);\n                $callID  = array_shift($json);\n                $procURI = array_shift($json);\n\n                if (count($json) == 1 && is_array($json[0])) {\n                    $json = $json[0];\n                }\n\n                $this->_decorating->onCall($from, $callID, $from->getUri($procURI), $json);\n            break;","sourceCodeStart":79,"sourceCodeEnd":115,"githubUrl":"https://github.com/ratchetphp/Ratchet/blob/e621c6c40bf684bbbb877102416ad5303d05a9cc/src/Ratchet/Wamp/ServerProtocol.php#L79-L115","documentation":"Thrown when a WAMP message's element at index 1 — the Topic/URI used by PREFIX, CALL, SUBSCRIBE, UNSUBSCRIBE and PUBLISH frames — is neither a string nor a number. The check `isset($json[1]) && !(is_string($json[1]) || is_numeric($json[1]))` guards the topic before dispatch, because `getUri()` and prefix lookup require a string topic URI.","triggerScenarios":"A client sends a frame like [5, {\"uri\": \"topic\"}] or [5, [\"topic\"]] or [5, null] where the second element is an array, object or null instead of a string topic URI. Note null at index 1 actually fails isset() and would not trigger; triggers specifically for arrays, booleans (after json_decode arrays) and nested structures.","commonSituations":"Client serialization bugs that nest the topic inside an object; WAMP v2-style publish options objects placed in the wrong position, e.g. [7, {exclude: ...}, topic] instead of legacy [7, topic]; hand-written protocol implementations misordering the frame elements.","solutions":["Ensure element 1 of every client-to-server WAMP frame (SUBSCRIBE/UNSUBSCRIBE/PUBLISH topic, CALL proc URI, PREFIX shortname) is a plain string, e.g. [5, \"http://example.com/topic\"].","Fix client-side frame construction order: WAMP v1 expects [type, topic, ...payload], not [type, options, topic].","Add a client-side serializer that validates the frame shape before sending: assert(typeof msg[1] === 'string').","On the server, catch the exception in onError and log the connection to identify which client is sending malformed frames."],"exampleFix":"// before (client)\nws.send(JSON.stringify([5, { uri: 'kittens' }]));\n// after\nws.send(JSON.stringify([5, 'kittens']));","handlingStrategy":"validation","validationCode":"$frame = json_decode($msg, true);\nif (isset($frame[1]) && !is_string($frame[1]) && !is_numeric($frame[1])) {\n    // reject: element 1 (topic/proc URI) must be a string or number\n}","typeGuard":"function hasValidWampTopic(array $frame): bool {\n    return !isset($frame[1]) || is_string($frame[1]) || is_numeric($frame[1]);\n}","tryCatchPattern":"try {\n    $server->onMessage($conn, $msg);\n} catch (\\Ratchet\\Wamp\\Exception $e) {\n    error_log(\"Bad topic from client: \" . $msg);\n    $conn->close();\n}","preventionTips":["Construct frames as [type, topicString, ...] with the topic always a plain string.","Never put WAMP v2-style option objects in position 1 of v1 frames.","Validate topic shape client-side (typeof topic === 'string') before send.","Log raw frames on server protocol errors to find the offending client quickly."],"tags":["websocket","wamp","topic-uri","protocol"],"backgroundTag":"invalid-argument-value","analyzedSha":"e621c6c40bf684bbbb877102416ad5303d05a9cc","analyzedAt":"2026-09-16T00:13:27.878Z","contentChangedAt":"2026-09-16T00:13:27.878Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}