{"record":{"id":"97a18b25b06e5060","repo":"phalcon/cphalcon","slug":"the-callback-parameter-must-be-either-a-callable","errorCode":null,"errorMessage":"The 'callback' parameter must be either a callable or NULL.","messagePattern":"The 'callback' parameter must be either a callable or NULL\\.","errorType":"exception","errorClass":"Phalcon\\Mvc\\Router\\Exceptions\\InvalidCallbackParameter","httpStatus":null,"severity":"error","filePath":"phalcon/Mvc/Router/Annotations.zep","lineNumber":479,"sourceCode":"     * // String as callback\n     * $annotationRouter->setActionPreformatCallback('strtolower');\n     *\n     * // If empty method constructor called [null], sets uncamelize with - delimiter\n     * $annotationRouter->setActionPreformatCallback();\n     * ```\n     *\n     * @param callable|string|null $callback\n     */\n    public function setActionPreformatCallback(var callback = null) -> <self>\n    {\n        if likely is_callable(callback) {\n            let this->actionPreformatCallback = callback;\n        } elseif callback === null {\n            let this->actionPreformatCallback = function (action) {\n                return uncamelize(action, \"-\");\n            };\n        } else {\n            throw new InvalidCallbackParameter();\n        }\n\n        return this;\n    }\n\n    /**\n     * @return callable|string|null\n     */\n    public function getActionPreformatCallback()\n    {\n        return this->actionPreformatCallback;\n    }\n\n    /**\n     * Changes the controller class suffix\n     */\n    public function setControllerSuffix( string controllerSuffix) -> <self>\n    {","sourceCodeStart":461,"sourceCodeEnd":497,"githubUrl":"https://github.com/phalcon/cphalcon/blob/b7419de9cd0a8a3f48441ead84c9f8415d463e25/phalcon/Mvc/Router/Annotations.zep#L461-L497","documentation":"AnnotationsRouter::setActionPreformatCallback() accepts only a valid callable (existing function name string, callable array, closure, or invokable object) or null (which installs a default uncamelize-with-dash callback). Any other value — false, an integer, a non-callable string, a plain object without __invoke — throws InvalidCallbackParameter.","triggerScenarios":"setActionPreformatCallback('underscore_action') where no such function exists; passing false or '' expecting to reset; passing an object that lacks __invoke; a string naming a method without a valid 'Class::method' static form.","commonSituations":"Trying to disable the preformat step by passing false; copy-pasting a callback name from another project; passing a configured value from a settings file unchecked.","solutions":["Pass null to (re)install the default uncamelize behavior","Pass a closure: $router->setActionPreformatCallback(fn ($action) => strtolower(preg_replace('/(?<!^)[A-Z]/', '_$0', $action)));","Verify is_callable($value) || $value === null before assigning values that come from config"],"exampleFix":"// before\n$router->setActionPreformatCallback('my_preformat'); // function does not exist\n$router->setActionPreformatCallback(false);         // not callable, not null\n\n// after\n$router->setActionPreformatCallback(\n    function (string $action): string {\n        return uncamelize($action, \"-\");\n    }\n);","handlingStrategy":"validation","validationCode":"// accept only null or verified callables from config before assignment\n$callback = $config['actionPreformatCallback'] ?? null;\n\nif ($callback !== null && !is_callable($callback)) {\n    throw new InvalidArgumentException(\n        'actionPreformatCallback must be callable or null, got ' . var_export($callback, true)\n    );\n}\n\n$router->setActionPreformatCallback($callback);","typeGuard":"function isPreformatCallback(mixed $cb): bool\n{\n    return $cb === null || is_callable($cb);\n}","tryCatchPattern":"try {\n    $router->setActionPreformatCallback($callback);\n} catch (\\Phalcon\\Mvc\\Router\\Exceptions\\InvalidCallbackParameter $e) {\n    $logger->error('Invalid action preformat callback: ' . get_debug_type($callback));\n    throw $e;\n}","preventionTips":["Pass null to reset to the default uncamelize behavior — never false or ''","Prefer closures so validity is checked by the engine at definition","Treat config-supplied callback names as untrusted: verify is_callable() during load"],"tags":["phalcon","router","annotations","callable","validation"],"backgroundTag":"callback-not-callable","analyzedSha":"b7419de9cd0a8a3f48441ead84c9f8415d463e25","analyzedAt":"2026-08-21T06:21:18.811Z","schemaVersion":2},"datasetVersion":"2026-08-21T11:28:35.574Z"}