{"record":{"id":"36bb9d38bcfb8afd","repo":"cakephp/cakephp","slug":"need-a-valid-closure-to-connect-routes","errorCode":null,"errorMessage":"Need a valid Closure to connect routes.","messagePattern":"Need a valid Closure to connect routes\\.","errorType":"exception","errorClass":"InvalidArgumentException","httpStatus":null,"severity":"error","filePath":"src/Routing/RouteBuilder.php","lineNumber":959,"sourceCode":"     *\n     * - `_namePrefix` Set a prefix used for named routes. The prefix is prepended to the\n     *   name of any route created in a scope callback.\n     *\n     * @param string $path The path to create a scope for.\n     * @param \\Closure|array $params Either the parameters to add to routes, or a callback.\n     * @param \\Closure|null $callback The callback to invoke that builds the plugin routes.\n     *   Only required when $params is defined.\n     * @return $this\n     * @throws \\InvalidArgumentException when there is no callable parameter.\n     */\n    public function scope(string $path, Closure|array $params, ?Closure $callback = null)\n    {\n        if ($params instanceof Closure) {\n            $callback = $params;\n            $params = [];\n        }\n        if ($callback === null) {\n            throw new InvalidArgumentException('Need a valid Closure to connect routes.');\n        }\n\n        if ($this->_path !== '/') {\n            $path = $this->_path . $path;\n        }\n        $namePrefix = $this->_namePrefix;\n        if (isset($params['_namePrefix'])) {\n            $namePrefix .= $params['_namePrefix'];\n        }\n        unset($params['_namePrefix']);\n\n        $params += $this->_params;\n        $builder = new static($this->_collection, $path, $params, [\n            'routeClass' => $this->_routeClass,\n            'extensions' => $this->_extensions,\n            'namePrefix' => $namePrefix,\n            'middleware' => $this->middleware,\n        ]);","sourceCodeStart":941,"sourceCodeEnd":977,"githubUrl":"https://github.com/cakephp/cakephp/blob/1128eba9b09f1946df684811350e26f2cef68fac/src/Routing/RouteBuilder.php#L941-L977","documentation":"RouteBuilder::scope() requires a Closure to define nested routes. If neither the $params argument nor the third argument is a Closure (i.e. $callback stays null), the builder throws this InvalidArgumentException, because a scope without a callback has no routes to connect.","triggerScenarios":"$routes->scope('/api') with no closure; passing something other than a Closure (e.g. an array or a string) as the callback; transposing the $params and $callback arguments.","commonSituations":"Refactor stubs leaving scope() without a body; copy-paste from non-closure route APIs; dynamic code building scopes where the callback variable is conditionally null.","solutions":["Pass a closure as the callback: $routes->scope('/api', function ($routes) { ... });","If you intended options, pass them in the array argument and still provide the Closure last: scope('/api', ['param' => 1], function ($routes) {...}).","Check for null callback variables before calling scope()."],"exampleFix":"// before\n$routes->scope('/api', 'routes');\n// after\n$routes->scope('/api', function (RouteBuilder $routes) {\n    $routes->connect('/ping', ['controller' => 'Ping', 'action' => 'index']);\n});","handlingStrategy":"type-guard","validationCode":"if (!$callback instanceof \\Closure) {\n    throw new InvalidArgumentException('scope() requires a Closure callback');\n}","typeGuard":"function isValidScopeCallback(mixed $cb): bool { return $cb instanceof \\Closure; }","tryCatchPattern":null,"preventionTips":["Always pass an anonymous function as the last argument to scope().","Keep argument order: path, params array (optional), Closure.","Add static analysis (PHPStan/Psalm) rules to catch non-Closure callbacks."],"tags":["php","cakephp","routing","missing-required-argument"],"backgroundTag":"missing-required-argument","analyzedSha":"1128eba9b09f1946df684811350e26f2cef68fac","analyzedAt":"2026-09-12T12:07:00.388Z","contentChangedAt":"2026-09-12T12:07:00.388Z","schemaVersion":2},"datasetVersion":"2026-09-19T12:17:13.211Z"}