{"record":{"id":"66368589bba4f79a","repo":"phalcon/cphalcon","slug":"before-match-callback-is-not-callable-in-matched-r","errorCode":null,"errorMessage":"Before-Match callback is not callable in matched route '{pattern}'","messagePattern":"Before-Match callback is not callable in matched route '(.+?)'","errorType":"exception","errorClass":"Phalcon\\Cli\\Router\\Exceptions\\BeforeMatchNotCallable","httpStatus":null,"severity":"error","filePath":"phalcon/Cli/Router.zep","lineNumber":265,"sourceCode":"\n                if memstr(pattern, \"^\") {\n                    let routeFound = preg_match(pattern, arguments, matches);\n                } else {\n                    let routeFound = pattern == arguments;\n                }\n\n                /**\n                 * Check for beforeMatch conditions\n                 */\n                if routeFound {\n                    let beforeMatch = route->getBeforeMatch();\n\n                    if beforeMatch !== null {\n                        /**\n                         * Check first if the callback is callable\n                         */\n                        if unlikely !is_callable(beforeMatch) {\n                            throw new BeforeMatchNotCallable(route->getPattern());\n                        }\n\n                        /**\n                         * Check first if the callback is callable\n                         */\n                        let routeFound = call_user_func_array(\n                            beforeMatch,\n                            [\n                                arguments,\n                                route,\n                                this\n                            ]\n                        );\n                    }\n                }\n\n                if routeFound {\n                    /**","sourceCodeStart":247,"sourceCodeEnd":283,"githubUrl":"https://github.com/phalcon/cphalcon/blob/b7419de9cd0a8a3f48441ead84c9f8415d463e25/phalcon/Cli/Router.zep#L247-L283","documentation":"Defensive recheck at match time: once a route matches and has a beforeMatch callback, the router runs is_callable() on it before invoking it with (arguments, route, router). Route::beforeMatch() already rejects non-callables at registration, so this variant fires when the callback becomes non-callable after the route was built.","triggerScenarios":"A custom Route subclass that sets the beforeMatch property directly or overrides getBeforeMatch(); routes restored from cache/serialization where a closure was lost to a string or the referenced method no longer exists.","commonSituations":"Caching or serializing route objects with closures (closures do not survive serialization); renaming/removing the referenced function or method after routes were registered; relying on __call magic for which is_callable returns false.","solutions":["Rebuild routes on each run instead of serializing them with beforeMatch closures","Ensure the callback target (function name or [object, method] pair) still exists at match time","If subclassing Route, keep getBeforeMatch() returning a valid callable or null"],"exampleFix":"// before (cached/serialized routes)\n$routes = unserialize($cache->get('cli-routes'));\n$router = new Router();\n// beforeMatch closures were lost on serialize -> match-time throw\n\n// after\n$router = new Router();\n$router->add('/backup', ['task' => 'backup'])\n       ->beforeMatch(function ($args, $route, $router) {\n           return php_sapi_name() === 'cli';\n       });","handlingStrategy":"validation","validationCode":"// Before matching, assert every route's beforeMatch survived registration/cache\nforeach ($router->getRoutes() as $route) {\n    $beforeMatch = $route->getBeforeMatch();\n    if ($beforeMatch !== null && !is_callable($beforeMatch)) {\n        throw new RuntimeException(\n            'Route ' . $route->getPattern() . ' has a non-callable beforeMatch'\n        );\n    }\n}\n$router->handle($_SERVER['argv'] ?? null);","typeGuard":null,"tryCatchPattern":"try {\n    $router->handle($arguments);\n} catch (\\Phalcon\\Cli\\Router\\Exception\\BeforeMatchNotCallable $e) {\n    // Callback degraded after registration (cache/serialization/subclass).\n    // Rebuild the route table without the cached beforeMatch and retry.\n    rebuildRoutes($router);\n    $router->handle($arguments);\n}","preventionTips":["Do not serialize/cache routes that carry closure beforeMatch callbacks — rebuild routes each run","If subclassing Route, keep getBeforeMatch() returning a valid callable or null","Add a smoke test that matches one route per module so callback regressions surface in CI"],"tags":["phalcon","cli","router","callback","callable"],"backgroundTag":"callback-not-callable","analyzedSha":"b7419de9cd0a8a3f48441ead84c9f8415d463e25","analyzedAt":"2026-08-21T06:21:18.811Z","schemaVersion":2},"datasetVersion":"2026-08-21T11:28:35.574Z"}