phalcon/cphalcon · error · InvalidPathType
'path' must be a string or a closure
Error message
'path' must be a string or a closure
What it means
Thrown by Volt\Compiler::compileFile()/compile() when the 'compiledPath' option is neither a string nor a Closure. Volt builds the compiled-template destination path from this option: a string is used as the directory prefix, a Closure is invoked with (templatePath, options, extendsMode) to compute it dynamically. Any other type (null, int, array, plain object) cannot be resolved into a destination, so compilation aborts before the template is read.
Source
Thrown at phalcon/Mvc/View/Engine/Volt/Compiler.zep:460
let compiledTemplatePath = compiledPath . prefix . templateSepPath . compiledExtension;
}
} elseif typeof compiledPath == "object" && compiledPath instanceof Closure {
/**
* A closure can dynamically compile the path
*/
let compiledTemplatePath = call_user_func_array(
compiledPath,
[templatePath, options, extendsMode]
);
/**
* The closure must return a valid path
*/
if unlikely typeof compiledTemplatePath != "string" {
throw new InvalidPathClosureReturn();
}
} else {
throw new InvalidPathType();
}
/**
* Compile always must be used only in the development stage
*/
if !this->phpFileExists(compiledTemplatePath) || compileAlways {
/**
* The file needs to be compiled because it either does not exist or
* needs to compiled every time
*/
let compilation = this->compileFile(
templatePath,
compiledTemplatePath,
extendsMode
);
} else {
if stat === true {
/**View on GitHub (pinned to b7419de9cd)
Solutions
- Set 'compiledPath' to a string directory, e.g. ['compiledPath' => appPath('/storage/cache/volt/')], or to a \Closure returning a string
- Verify the exact option names when registering the Volt service ('compiledPath', 'compiledSeparator', 'compiledExtension', 'stat', 'compileAlways')
- If using a Closure, confirm it is a real \Closure and that it returns a string (returning non-string throws the sibling InvalidPathClosureReturn)
Example fix
// before
$volt->setOptions(['compiledPath' => null]); // or typo: 'compiled_path'
// after
$volt->setOptions([
'compiledPath' => appPath('/storage/cache/volt/'),
'compileAlways' => false,
]); Defensive patterns
Strategy: validation
Validate before calling
$options = $volt->getOptions();
$compiledPath = $options['compiledPath'] ?? null;
if (!is_string($compiledPath) && !($compiledPath instanceof \Closure)) {
throw new \InvalidArgumentException(
"Volt 'compiledPath' must be a string or Closure, got " . gettype($compiledPath)
);
} Type guard
function isValidVoltCompiledPath(mixed $path): bool
{
return is_string($path) || $path instanceof \Closure;
} Prevention
- Register the Volt service from one central factory so compiledPath is always set with the right type
- Add a boot-time assertion on Volt options before the first render
- Use is_string()/instanceof Closure checks when compiledPath comes from user/config input
When it happens
Trigger: Calling $compiler->compile('view.volt') or $volt->render() while the 'compiledPath' option is null, an array, an integer, or a non-Closure object. Typical when the Volt service is registered with a typo'd key (e.g. 'compiled_path') so compiledPath stays null, or when a config value that should be a string is loaded as another type.
Common situations: Misconfigured Volt service in the DI container (wrong option name, null from missing config entry), passing an array of options where the path string is expected, or a Closure import mismatch (a string class reference instead of an actual Closure instance).
Understand the failure class
Background: Invalid argument type errors: "must be of type string", "expected X, got Y", and ERR_INVALID_ARG_TYPE explained — this error's family across 15 libraries.
Related errors
- Corrupted statement
- The extension is not valid
- 'always' must be a bool value
- 'prefix' must be a string
- 'separator' must be a string
AI-assisted analysis of phalcon/cphalcon@b7419de9cd (2026-08-21).
Data as JSON: /api/errors/731ff29f24513a86.
Report an issue: GitHub.