phalcon/cphalcon · error · Phalcon\Mvc\View\Exceptions\ViewServicesUnavailable
A dependency injection container is required to access appli
Error message
A dependency injection container is required to access application services
What it means
View::registerEngines() records engine definitions but they are lazily resolved on first render, when a DI container is needed to instantiate engines from service names (phalcon/Mvc/View.zep:1308). If registered engines exist and the attached container is not an object, ViewServicesUnavailable is thrown — the default single-engine path (plain PhpEngine) needs no container, so this only fires once you register custom engines.
Source
Thrown at phalcon/Mvc/View.zep:1308
let engines = this->engines;
/**
* If the engines aren't initialized 'engines' is false
*/
if engines === false {
let di = <DiInterface> this->container;
let engines = [];
let registeredEngines = this->registeredEngines;
if empty registeredEngines {
/**
* We use Phalcon\Mvc\View\Engine\Php as default
*/
let engines[".phtml"] = new PhpEngine(this, di);
} else {
if typeof di != "object" {
throw new ViewServicesUnavailable();
}
for extension, engineService in registeredEngines {
if typeof engineService == "object" {
/**
* Engine can be a closure
*/
if engineService instanceof Closure {
let engineService = Closure::bind(
engineService,
di
);
let engines[extension] = call_user_func(
engineService,
this
);
} else {View on GitHub (pinned to b7419de9cd)
Solutions
- Attach the container once at setup: $view->setDI($di) before any render
- Reuse the app's DI in workers/tests instead of a bare new View()
- If you only need plain PHP templates, remove registerEngines() calls so the PhpEngine default (no container needed) is used
Example fix
// before
$view = new \Phalcon\Mvc\View();
$view->setViewsDir($dir);
$view->registerEngines(['.volt' => 'voltService']);
echo $view->render('mails/welcome'); // no DI -> ViewServicesUnavailable
// after
$view = new \Phalcon\Mvc\View();
$view->setViewsDir($dir);
$view->setDI($app->getDI());
$view->registerEngines(['.volt' => 'voltService']);
echo $view->render('mails/welcome'); Defensive patterns
Strategy: validation
Validate before calling
if ($view->getRegisteredEngines() && $view->getDI() === null) {
throw new \RuntimeException('Custom view engines require a DI container; call View::setDI()');
} Prevention
- Always pair registerEngines() with setDI() in the same setup block
- Reuse the application DI in CLI workers and tests
- Skip registerEngines() if only .phtml templates are rendered
When it happens
Trigger: $view = new View(); $view->registerEngines(['.volt' => ...]); $view->render(...) without ever calling setDI(); rendering a view template from a queue worker or CLI script where the DI was not bootstrapped; unit tests constructing View directly.
Common situations: CLI email-rendering workers that reuse View but skip full app bootstrap; test harnesses that new up View; refactoring a controller-rendered template into a background job without carrying the container.
Related errors
- Invalid template engine registration for extension: {extensi
- Cannot extend already-resolved service '{name}'
- Argument at position {} must have a type
- Service '{}' is required in parameter on position {}
- Service 'value' is required in parameter on position {}
AI-assisted analysis of phalcon/cphalcon@b7419de9cd (2026-08-21).
Data as JSON: /api/errors/773eaad169dff640.
Report an issue: GitHub.