{"record":{"id":"e6763331dd567410","repo":"ratchetphp/Ratchet","slug":"all-routes-must-implement-ratchet-http-httpserverinterface","errorCode":null,"errorMessage":"All routes must implement Ratchet\\Http\\HttpServerInterface","messagePattern":"All routes must implement Ratchet\\\\Http\\\\HttpServerInterface","errorType":"exception","errorClass":"UnexpectedValueException","httpStatus":null,"severity":"error","filePath":"src/Ratchet/Http/Router.php","lineNumber":56,"sourceCode":"\n        $context = $this->_matcher->getContext();\n        $context->setMethod($request->getMethod());\n        $context->setHost($uri->getHost());\n\n        try {\n            $route = $this->_matcher->match($uri->getPath());\n        } catch (MethodNotAllowedException $nae) {\n            return $this->close($conn, 405, array('Allow' => $nae->getAllowedMethods()));\n        } catch (ResourceNotFoundException $nfe) {\n            return $this->close($conn, 404);\n        }\n\n        if (is_string($route['_controller']) && class_exists($route['_controller'])) {\n            $route['_controller'] = new $route['_controller'];\n        }\n\n        if (!($route['_controller'] instanceof HttpServerInterface)) {\n            throw new \\UnexpectedValueException('All routes must implement Ratchet\\Http\\HttpServerInterface');\n        }\n\n        $parameters = [];\n        foreach($route as $key => $value) {\n            if ((is_string($key)) && ('_' !== substr($key, 0, 1))) {\n                $parameters[$key] = $value;\n            }\n        }\n        $parameters = array_merge($parameters, Query::parse($uri->getQuery() ?: ''));\n\n        $request = $request->withUri($uri->withQuery(Query::build($parameters)));\n\n        $conn->controller = $route['_controller'];\n        $conn->controller->onOpen($conn, $request);\n    }\n\n    /**\n     * {@inheritdoc}","sourceCodeStart":38,"sourceCodeEnd":74,"githubUrl":"https://github.com/ratchetphp/Ratchet/blob/e621c6c40bf684bbbb877102416ad5303d05a9cc/src/Ratchet/Http/Router.php#L38-L74","documentation":"After the Router matches the incoming request's host/path to a route and instantiates the controller (either the object registered with the router or a string class name that it news up), it verifies the controller implements Ratchet\\Http\\HttpServerInterface. If not, it throws UnexpectedValueException, because every routed endpoint must expose onOpen/onMessage/onClose/onError to receive the connection. This protects against registering arbitrary classes or string class names of the wrong type as routes.","triggerScenarios":"Registering a route whose controller is a plain class or closure that does not implement HttpServerInterface; registering a controller as a string class name (e.g. 'App\\Handler') where the class exists but only implements MessageComponentInterface or nothing at all; a typo causing Router to resolve the wrong controller entry from the route matcher's output.","commonSituations":"Migrating an app from Ratchet WsServer-style handlers to HTTP routes while reusing old MessageComponentInterface controllers; defining a route handler that expects FastRoute-style callables; refactoring that renamed/moved the controller class behind an interface that is no longer HttpServerInterface.","solutions":["Make the controller class implement Ratchet\\Http\\HttpServerInterface (e.g. extend Ratchet\\Http\\HttpServer or implement onOpen/onMessage/onClose/onError with RequestInterface in onOpen).","If you want WebSocket semantics at that route, wrap the handler in Ratchet\\WebSocket\\WsServer, which itself implements HttpServerInterface, before registering it.","Double-check the string class name registered for the route; class_exists() alone passes, so confirm the resolved class is the intended HTTP handler.","If the handler only needs HTTP request/response semantics, extend Ratchet\\Http\\HttpServer and override onOpen to return a ResponseInterface instead of expecting message streaming."],"exampleFix":"// before\n$router->addRoute('GET', '/ws', ChatHandler::class); // ChatHandler implements MessageComponentInterface only\n\n// after\nuse Ratchet\\WebSocket\\WsServer;\n$router->addRoute('GET', '/ws', new WsServer(new ChatHandler())); // WsServer implements HttpServerInterface","handlingStrategy":"validation","validationCode":"use Ratchet\\Http\\HttpServerInterface;\n$controller = is_string($route['_controller']) ? new $route['_controller'] : $route['_controller'];\nif (!$controller instanceof HttpServerInterface) {\n    throw new \\LogicException(get_class($controller) . ' must implement HttpServerInterface before being routed');\n}","typeGuard":"function isRoutableHandler($handler): bool {\n    return $handler instanceof \\Ratchet\\Http\\HttpServerInterface;\n}","tryCatchPattern":"try {\n    $router->onOpen($conn, $request);\n} catch (\\UnexpectedValueException $e) {\n    if (str_contains($e->getMessage(), 'HttpServerInterface')) {\n        error_log('Route controller does not implement HttpServerInterface: ' . $e->getMessage());\n    } else { throw $e; }\n}","preventionTips":["Every class registered as a route target should extend Ratchet\\Http\\HttpServer or wrap its handler in WsServer.","Add a boot-time smoke test that news up each route controller and asserts instanceof HttpServerInterface.","Avoid string class names in routes when possible; register instances so type errors surface at wiring time.","When reusing MessageComponentInterface handlers, always wrap them in WsServer before adding the route."],"tags":["php","ratchet","router","interface"],"backgroundTag":"type-mismatch","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"}