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

  1. Attach the container once at setup: $view->setDI($di) before any render
  2. Reuse the app's DI in workers/tests instead of a bare new View()
  3. 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

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


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