ratchetphp/Ratchet · error · UnexpectedValueException

All routes must implement Ratchet\Http\HttpServerInterface

Error message

All routes must implement Ratchet\Http\HttpServerInterface

What it means

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.

Solutions

  1. Make the controller class implement Ratchet\Http\HttpServerInterface (e.g. extend Ratchet\Http\HttpServer or implement onOpen/onMessage/onClose/onError with RequestInterface in onOpen).
  2. If you want WebSocket semantics at that route, wrap the handler in Ratchet\WebSocket\WsServer, which itself implements HttpServerInterface, before registering it.
  3. Double-check the string class name registered for the route; class_exists() alone passes, so confirm the resolved class is the intended HTTP handler.
  4. 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.

Example fix

// before
$router->addRoute('GET', '/ws', ChatHandler::class); // ChatHandler implements MessageComponentInterface only

// after
use Ratchet\WebSocket\WsServer;
$router->addRoute('GET', '/ws', new WsServer(new ChatHandler())); // WsServer implements HttpServerInterface
Defensive patterns

Strategy: validation

Validate before calling

use Ratchet\Http\HttpServerInterface;
$controller = is_string($route['_controller']) ? new $route['_controller'] : $route['_controller'];
if (!$controller instanceof HttpServerInterface) {
    throw new \LogicException(get_class($controller) . ' must implement HttpServerInterface before being routed');
}

Type guard

function isRoutableHandler($handler): bool {
    return $handler instanceof \Ratchet\Http\HttpServerInterface;
}

Try / catch

try {
    $router->onOpen($conn, $request);
} catch (\UnexpectedValueException $e) {
    if (str_contains($e->getMessage(), 'HttpServerInterface')) {
        error_log('Route controller does not implement HttpServerInterface: ' . $e->getMessage());
    } else { throw $e; }
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.

Related errors


AI-assisted analysis of ratchetphp/Ratchet@e621c6c40b (2026-09-16). Data as JSON: /api/errors/e6763331dd567410. Report an issue: GitHub.

Appendix: source

Thrown at src/Ratchet/Http/Router.php:56

        $context = $this->_matcher->getContext();
        $context->setMethod($request->getMethod());
        $context->setHost($uri->getHost());

        try {
            $route = $this->_matcher->match($uri->getPath());
        } catch (MethodNotAllowedException $nae) {
            return $this->close($conn, 405, array('Allow' => $nae->getAllowedMethods()));
        } catch (ResourceNotFoundException $nfe) {
            return $this->close($conn, 404);
        }

        if (is_string($route['_controller']) && class_exists($route['_controller'])) {
            $route['_controller'] = new $route['_controller'];
        }

        if (!($route['_controller'] instanceof HttpServerInterface)) {
            throw new \UnexpectedValueException('All routes must implement Ratchet\Http\HttpServerInterface');
        }

        $parameters = [];
        foreach($route as $key => $value) {
            if ((is_string($key)) && ('_' !== substr($key, 0, 1))) {
                $parameters[$key] = $value;
            }
        }
        $parameters = array_merge($parameters, Query::parse($uri->getQuery() ?: ''));

        $request = $request->withUri($uri->withQuery(Query::build($parameters)));

        $conn->controller = $route['_controller'];
        $conn->controller->onOpen($conn, $request);
    }

    /**
     * {@inheritdoc}

View on GitHub (pinned to e621c6c40b)