{"record":{"id":"5bcc9b5aed69c8dd","repo":"ratchetphp/Ratchet","slug":"argument-4-loop-expected-null-react-eventloop-loopinterface","errorCode":null,"errorMessage":"Argument #4 ($loop) expected null|React\\EventLoop\\LoopInterface","messagePattern":"Argument #4 \\(\\$loop\\) expected null\\|React\\\\EventLoop\\\\LoopInterface","errorType":"validation","errorClass":"InvalidArgumentException","httpStatus":null,"severity":"error","filePath":"src/Ratchet/App.php","lineNumber":72,"sourceCode":"\n    /**\n     * @var int\n     */\n    protected $_routeCounter = 0;\n\n    /**\n     * @param string         $httpHost   HTTP hostname clients intend to connect to. MUST match JS `new WebSocket('ws://$httpHost');`\n     * @param int            $port       Port to listen on. If 80, assuming production, Flash on 843 otherwise expecting Flash to be proxied through 8843\n     * @param string         $address    IP address to bind to. Default is localhost/proxy only. '0.0.0.0' for any machine.\n     * @param ?LoopInterface $loop       Specific React\\EventLoop to bind the application to. null will create one for you.\n     * @param array          $context\n     */\n    public function __construct($httpHost = 'localhost', $port = 8080, $address = '127.0.0.1', $loop = null, $context = array()) {\n        if (extension_loaded('xdebug') && getenv('RATCHET_DISABLE_XDEBUG_WARN') === false) {\n            trigger_error('XDebug extension detected. Remember to disable this if performance testing or going live!', E_USER_WARNING);\n        }\n        if ($loop !== null && !$loop instanceof LoopInterface) { // manual type check to support legacy PHP < 7.1\n            throw new \\InvalidArgumentException('Argument #4 ($loop) expected null|React\\EventLoop\\LoopInterface');\n        }\n\n        if (null === $loop) {\n            // prefer default Loop (reactphp/event-loop v1.2+) over legacy \\React\\EventLoop\\Factory\n            $loop = class_exists('React\\EventLoop\\Loop') ? Loop::get() : LegacyLoopFactory::create();\n        }\n\n        $this->httpHost = $httpHost;\n        $this->port = $port;\n\n        // prefer SocketServer (reactphp/socket v1.9+) over legacy \\React\\Socket\\Server\n        $socket = class_exists('React\\Socket\\SocketServer') ? new SocketServer($address . ':' . $port, $context, $loop) : new LegacySocketServer($address . ':' . $port, $loop, $context);\n\n        $this->routes  = new RouteCollection;\n        $this->_server = new IoServer(new HttpServer(new Router(new UrlMatcher($this->routes, new RequestContext))), $socket, $loop);\n\n        $policy = new FlashPolicy;\n        $policy->addAllowedAccess($httpHost, 80);","sourceCodeStart":54,"sourceCodeEnd":90,"githubUrl":"https://github.com/ratchetphp/Ratchet/blob/e621c6c40bf684bbbb877102416ad5303d05a9cc/src/Ratchet/App.php#L54-L90","documentation":"Ratchet\\App's constructor performs a manual runtime type check on its fourth argument because the code must support legacy PHP versions that predate nullable type hints. Passing $loop that is neither null nor a React\\EventLoop\\LoopInterface instance triggers this InvalidArgumentException. $loop is normally null, letting Ratchet pick the default loop via React\\EventLoop\\Loop::get() or the legacy Factory.","triggerScenarios":"Passing a string like 'default' or 'ev' as the 4th constructor argument (a common confusion with old docs); passing a different event loop implementation such as a plain Swoole or Amp loop that does not implement LoopInterface; misordering positional arguments so something else lands in $loop.","commonSituations":"Upgrading reactphp/event-loop across major versions and keeping a stale loop factory call whose return type changed; copy-paste of constructor snippets from blogs using old signatures; attempting to share a loop from another async framework.","solutions":["Pass an instance of React\\EventLoop\\LoopInterface (e.g. React\\EventLoop\\Loop::get()) as the 4th argument, or omit it entirely so Ratchet creates the default loop.","If sharing a loop with other reactphp components, create it once via Loop::get() and pass that same instance everywhere.","Check argument order: __construct($httpHost, $port, $address, $loop, $context) — ensure no earlier positional argument shifted.","Replace legacy \\React\\EventLoop\\Factory::create() calls with Loop::get() when on reactphp/event-loop v1.2+."],"exampleFix":"// before\n$app = new Ratchet\\App('0.0.0.0', 8080, '0.0.0.0', 'default');\n// after\n$app = new Ratchet\\App('0.0.0.0', 8080, '0.0.0.0', React\\EventLoop\\Loop::get());","handlingStrategy":"type-guard","validationCode":"$loop = $loop ?? \\React\\EventLoop\\Loop::get();\nif (!$loop instanceof \\React\\EventLoop\\LoopInterface) {\n    throw new \\TypeError('App() $loop must be a React\\EventLoop\\LoopInterface or null');\n}","typeGuard":"function isReactLoop($loop): bool {\n    return $loop === null || $loop instanceof \\React\\EventLoop\\LoopInterface;\n}","tryCatchPattern":"try {\n    $app = new Ratchet\\App('localhost', 8080, '127.0.0.1', $loop);\n} catch (\\InvalidArgumentException $e) {\n    $app = new Ratchet\\App('localhost', 8080); // fall back to default loop\n}","preventionTips":["Omit the $loop argument unless you specifically share a loop across components.","Use React\\EventLoop\\Loop::get() as the canonical loop instance on reactphp/event-loop v1.2+.","Check positional argument order ($httpHost, $port, $address, $loop, $context) when passing by position.","Use named arguments in PHP 8+ to avoid positional mix-ups."],"tags":["php","event-loop","constructor","reactphp"],"backgroundTag":"invalid-constructor-argument","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"}