phalcon/cphalcon · error · Phalcon\Mvc\Router\Exceptions\InvalidCallbackParameter

The 'callback' parameter must be either a callable or NULL.

Error message

The 'callback' parameter must be either a callable or NULL.

What it means

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.

Source

Thrown at phalcon/Mvc/Router/Annotations.zep:479

     * // String as callback
     * $annotationRouter->setActionPreformatCallback('strtolower');
     *
     * // If empty method constructor called [null], sets uncamelize with - delimiter
     * $annotationRouter->setActionPreformatCallback();
     * ```
     *
     * @param callable|string|null $callback
     */
    public function setActionPreformatCallback(var callback = null) -> <self>
    {
        if likely is_callable(callback) {
            let this->actionPreformatCallback = callback;
        } elseif callback === null {
            let this->actionPreformatCallback = function (action) {
                return uncamelize(action, "-");
            };
        } else {
            throw new InvalidCallbackParameter();
        }

        return this;
    }

    /**
     * @return callable|string|null
     */
    public function getActionPreformatCallback()
    {
        return this->actionPreformatCallback;
    }

    /**
     * Changes the controller class suffix
     */
    public function setControllerSuffix( string controllerSuffix) -> <self>
    {

View on GitHub (pinned to b7419de9cd)

Solutions

  1. Pass null to (re)install the default uncamelize behavior
  2. Pass a closure: $router->setActionPreformatCallback(fn ($action) => strtolower(preg_replace('/(?<!^)[A-Z]/', '_$0', $action)));
  3. Verify is_callable($value) || $value === null before assigning values that come from config

Example fix

// before
$router->setActionPreformatCallback('my_preformat'); // function does not exist
$router->setActionPreformatCallback(false);         // not callable, not null

// after
$router->setActionPreformatCallback(
    function (string $action): string {
        return uncamelize($action, "-");
    }
);
Defensive patterns

Strategy: validation

Validate before calling

// accept only null or verified callables from config before assignment
$callback = $config['actionPreformatCallback'] ?? null;

if ($callback !== null && !is_callable($callback)) {
    throw new InvalidArgumentException(
        'actionPreformatCallback must be callable or null, got ' . var_export($callback, true)
    );
}

$router->setActionPreformatCallback($callback);

Type guard

function isPreformatCallback(mixed $cb): bool
{
    return $cb === null || is_callable($cb);
}

Try / catch

try {
    $router->setActionPreformatCallback($callback);
} catch (\Phalcon\Mvc\Router\Exceptions\InvalidCallbackParameter $e) {
    $logger->error('Invalid action preformat callback: ' . get_debug_type($callback));
    throw $e;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of phalcon/cphalcon@b7419de9cd (2026-08-21). Data as JSON: /api/errors/97a18b25b06e5060. Report an issue: GitHub.