phalcon/cphalcon · error · Phalcon\Cli\Console\Exceptions\InvalidModuleDefinition
Invalid module definition for module '{moduleName}': The mod
Error message
Invalid module definition for module '{moduleName}': The module definition object must be a Closure What it means
The other branch of CLI module loading: when the definition is an object (not an array), Phalcon requires it to be a Closure. The closure is invoked with the DI container via call_user_func_array and must return an object implementing the module definition interface (registerAutoloaders/registerServices). Passing any other object — even an already-constructed module instance — throws this.
Source
Thrown at phalcon/Cli/Console.zep:145
if !class_exists(className, false) {
require_once path;
}
}
let moduleObject = <ModuleDefinitionInterface> this->container->get(className);
/**
* 'registerAutoloaders' and 'registerServices' are
* automatically called
*/
moduleObject->registerAutoloaders(this->container);
moduleObject->registerServices(this->container);
} else {
/**
* A module definition object, can be a Closure instance
*/
if unlikely !(module instanceof Closure) {
throw new InvalidModuleDefinition(
moduleName,
"The module definition object must be a Closure"
);
}
let moduleObject = call_user_func_array(
module,
[
this->container
]
);
}
/**
* The "afterStartModule" event is fired once the module has
* started. Unlike Phalcon\Mvc\Application - where the return value
* is a notification only - Console honors a `false` return and
* aborts handling. This divergence is retained for backwardView on GitHub (pinned to b7419de9cd)
Solutions
- Wrap construction in a closure: 'api' => function ($di) { return new \App\Api\Module($di->get('logger')); }
- Or switch to the array definition with className/path and let the DI container construct the module
Example fix
// before
$console->registerModules([
'api' => new \App\Api\Module($logger),
]);
// after
$console->registerModules([
'api' => function ($di) {
return new \App\Api\Module($di->get('logger'));
},
]); Defensive patterns
Strategy: validation
Validate before calling
foreach ($modules as $name => $definition) {
if (is_object($definition) && !$definition instanceof \Closure) {
throw new InvalidArgumentException(sprintf(
'Module "%s" object definition must be a Closure, got %s',
$name,
get_class($definition)
));
}
} Type guard
/**
* Object module definitions must be Closures; anything else
* (including module instances) is rejected by Cli\Console::handle().
*/
function isClosureModuleDefinition($definition): bool
{
return $definition instanceof \Closure;
} Try / catch
try {
$console->handle();
} catch (\Phalcon\Cli\Console\Exception\InvalidModuleDefinition $e) {
if (strpos($e->getMessage(), 'Closure') !== false) {
// Object definition branch: wrap the instance in a closure instead
$stderr->writeln('Wrap object module definitions in a Closure: ' . $e->getMessage());
}
exit(1);
} Prevention
- Never register a pre-constructed module instance; wrap it in a closure that receives the container
- Use the className/path array form when the module has no constructor arguments
When it happens
Trigger: $console->registerModules(['api' => new \App\Api\Module($logger)]) — a ready-made instance instead of a Closure; also any stdClass or helper object used as the definition.
Common situations: Developers instantiate the module class directly because it needs constructor arguments, instead of wrapping construction in a closure or using the className/path array form.
Related errors
- Invalid module definition for module '{moduleName}': The mod
- Module definition path '{path}' does not exist
- Arguments must be an array or string, {type} given
- Before-Match callback is not callable in matched route '{pat
- Before-Match callback is not callable in matched route '{pat
AI-assisted analysis of phalcon/cphalcon@b7419de9cd (2026-08-21).
Data as JSON: /api/errors/1877ab7de3659a58.
Report an issue: GitHub.