ratchetphp/Ratchet · error · UnexpectedValueException
$request can not be null
Error message
$request can not be null
What it means
Ratchet's Http\Router::onOpen() receives the PSR-7 RequestInterface that was attached to the connection by an upstream HttpServer component. It throws UnexpectedValueException immediately when $request is null, because routing (host/path matching) is impossible without a request. This guards against the Router being wired into a stack that never injects a request, or onOpen() being called directly with only a connection.
Solutions
- Ensure Router sits under an HttpServer component: new IoServer(new HttpServer(new Router(...), ...), ...) so onOpen receives a PSR-7 request.
- If invoking onOpen in tests, pass a valid RequestInterface instance implementing PSR-7 (e.g. GuzzleHttp\Psr7\ServerRequest) as the second argument.
- Audit any custom components between HttpServer and Router to confirm they preserve $conn->httpHeadersReceived / forward the request rather than calling onOpen($conn) themselves.
- Verify you are not wrapping Router directly in WsServer/IoServer in a way that bypasses the HTTP handshake that produces the request.
Example fix
// before $server = new IoServer(new Router($routeParser), $sock, $loop); // after use Ratchet\Http\HttpServer; $server = new IoServer(new HttpServer(new Router($routeParser)), $sock, $loop);
Defensive patterns
Strategy: try-catch
Validate before calling
use Psr\Http\Message\RequestInterface;
if (!isset($request) || !$request instanceof RequestInterface) {
throw new \LogicException('Router::onOpen requires a PSR-7 RequestInterface; mount Router under HttpServer.');
} Type guard
function hasRequest($conn): bool {
return isset($conn->httpHeadersReceived) && $conn->httpHeadersReceived instanceof \Psr\Http\Message\RequestInterface;
} Try / catch
try {
$router->onOpen($conn, $request);
} catch (\UnexpectedValueException $e) {
if ($e->getMessage() === '$request can not be null') {
$conn->close(); // stack misconfigured: Router never receives a request
} else { throw $e; }
} Prevention
- Always mount Router beneath Ratchet\Http\HttpServer in the component stack.
- In tests, construct a real PSR-7 request (GuzzleHttp\Psr7\ServerRequest) rather than passing only a connection.
- Never call Router::onOpen($conn) with one argument; grep for that pattern in middleware.
- Keep HttpServer as the immediate parent of Router when composing components.
When it happens
Trigger: Calling Router::onOpen($conn) manually without the second argument; putting Router directly under IoServer instead of under HttpServer (which normally provides the PSR-7 request); a custom middleware/component in the socket stack that consumes or fails to re-attach the request before Router runs.
Common situations: Reordering components in the app stack so Router runs before HttpServer; unit tests invoking onOpen with a mock connection only; upgrading Ratchet or swapping the HTTP handshake component so the request no longer reaches the Router.
Related errors
- All routes must implement Ratchet\Http\HttpServerInterface
- Invalid domain
- Invalid Port
- Invalid site control set
- Expected instance of…
AI-assisted analysis of ratchetphp/Ratchet@e621c6c40b (2026-09-16).
Data as JSON: /api/errors/e61952ed754a64de.
Report an issue: GitHub.
Appendix: source
Thrown at src/Ratchet/Http/Router.php:32
* @var \Symfony\Component\Routing\Matcher\UrlMatcherInterface
*/
protected $_matcher;
private $_noopController;
public function __construct(UrlMatcherInterface $matcher) {
$this->_matcher = $matcher;
$this->_noopController = new NoOpHttpServerController;
}
/**
* {@inheritdoc}
* @throws \UnexpectedValueException If a controller is not \Ratchet\Http\HttpServerInterface
*/
#[HackSupportForPHP8] public function onOpen(ConnectionInterface $conn, ?RequestInterface $request = null) { /*
public function onOpen(ConnectionInterface $conn, RequestInterface $request = null) { /**/
if (null === $request) {
throw new \UnexpectedValueException('$request can not be null');
}
$conn->controller = $this->_noopController;
$uri = $request->getUri();
$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);
}
View on GitHub (pinned to e621c6c40b)