phalcon/cphalcon · error · InvalidUserFunctionDefinition

Invalid definition for user function '{name}' in {file} on l

Error message

Invalid definition for user function '{name}' in {file} on line {line}

What it means

The Volt template compiler throws this when a template calls a user-registered function whose definition is not usable. In Compiler.zep:1847 a definition registered via Compiler::addFunction() is only accepted if it is a string (macro) or a Closure (invoked with the resolved arguments); any other value (array callable, integer, plain object) reaches the throw. The message carries the function name and the exact template file/line that invoked it. It surfaces at compile time, i.e. the first time the template is rendered (or when the compiled cache is cold).

Source

Thrown at phalcon/Mvc/View/Engine/Volt/Compiler.zep:1847

                     */
                    if typeof definition == "string" {
                        return definition . "(" . arguments . ")";
                    }

                    /**
                     * Execute the function closure returning the compiled
                     * definition
                     */
                    if typeof definition == "object" {
                        if definition instanceof Closure {
                            return call_user_func_array(
                                definition,
                                [arguments, funcArguments]
                            );
                        }
                    }

                    throw new InvalidUserFunctionDefinition((string) name, (string) expr["file"], (int) expr["line"]);
                }
            }

            /**
             * This function includes the previous rendering stage
             */
            if name == "get_content" || name == "content" {
                return "$this->getContent()";
            }

            /**
             * This function includes views of volt or others template engines
             * dynamically
             */
            if name == "partial" {
                return "$this->partial(" . arguments . ")";
            }

View on GitHub (pinned to b7419de9cd)

Solutions

  1. Change the definition to a Closure: $compiler->addFunction('myFunc', function ($resolvedArgs) { return 'my_php_func(' . $resolvedArgs . ')'; }) — the Closure must return a string of PHP code, not a value
  2. Or use the string macro form if it maps 1:1 to a PHP function: $compiler->addFunction('myFunc', 'my_php_func')
  3. If you wanted runtime logic, keep the Closure but return a valid PHP expression string that the compiled template will evaluate
  4. Register functions in a shared 'volt' service via the DI container so every view uses the same registrations

Example fix

// before
$voltCompiler->addFunction('highlight', ['Util', 'highlight']);
{{ highlight(title) }}

// after
$voltCompiler->addFunction(
    'highlight',
    function ($resolvedArgs) {
        return 'Util::highlight(' . $resolvedArgs . ')';
    }
);
Defensive patterns

Strategy: validation

Validate before calling

$ok = is_string($definition) || $definition instanceof \Closure;
if (!$ok) {
    throw new \InvalidArgumentException(
        'Volt function definitions must be a string or Closure, got '
        . gettype($definition)
    );
}
$compiler->addFunction('myFunc', $definition);

Type guard

function isVoltCallableDefinition($definition): bool
{
    return is_string($definition) || $definition instanceof \Closure;
}

Try / catch

try {
    $view->render('page/index', $params);
} catch (\Phalcon\Mvc\View\Engine\Volt\Exception $e) {
    // message includes the template file/line of the failing call
    $logger->error('Volt compile failed: ' . $e->getMessage());
    echo 'Template temporarily unavailable.';
}

Prevention

When it happens

Trigger: Calling $compiler->addFunction('myFunc', ['MyClass','myMethod']) (PHP array-style callable), or addFunction('myFunc', 123), then using {{ myFunc(x) }} in a .volt template. Also passing a plain object that is not a Closure, or a definition that was coerced to the wrong type by config loading.

Common situations: Developers porting plain PHP helpers to Volt register them with array callables ['Str', 'method'] or 'Class::method' with 'new' syntax, both rejected. Definitions pulled from a config file or container service can arrive as non-Closure objects. The error only fires when the template is actually compiled, so a bad registration can pass tests that never render that template.

Related errors


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