phalcon/cphalcon · error · Phalcon\Tag\Exception

A dependency injection container is required to access the '

Error message

A dependency injection container is required to access the 'escaper' service

What it means

Phalcon\Tag's HTML helpers escape output through the 'escaper' shared service resolved from the default DI container (Tag::getDI() → Di::getDefault(); the resolved EscaperInterface is then cached statically in Tag::$escaperService). If no default container exists, getEscaperService() throws Phalcon\Tag\Exception ("A dependency injection container is required to access the 'escaper' service"). FactoryDefault registers 'escaper' (Phalcon\Html\Escaper) automatically — the error means Tag ran outside a booted application.

Source

Thrown at phalcon/Tag.zep:418

        }

        return self::getEscaperService();
    }

    /**
     * Returns an Escaper service from the default DI
     */
    public static function getEscaperService() -> <EscaperInterface>
    {
        var escaper, container;

        let escaper = self::escaperService;

        if typeof escaper != "object" {
            let container = self::getDI();

            if container === null {
                throw new Exception(
                    "A dependency injection container is required to access the 'escaper' service"
                );
            }

            let escaper = <EscaperInterface> container->getShared("escaper"),
                self::escaperService = escaper;
        }

        return escaper;
    }

    /**
     * Gets the current document title. The title will be automatically escaped.
     */
    public static function getTitle(
        bool prepend = true,
        bool append = true
    ) -> string {

View on GitHub (pinned to b7419de9cd)

Solutions

  1. Set a container before rendering: Tag::setDI($di) with $di->setShared('escaper', Escaper::class), or Di::setDefault(new FactoryDefault())
  2. In tests, prime the container once in setUp() and reset it in tearDown() to avoid order dependence
  3. Restructure code so tag helpers are only used inside a fully bootstrapped request

Example fix

// before (CLI script / unit test)
echo \Phalcon\Tag::textField(['name' => 'email']); // throws

// after
$di = new \Phalcon\Di\Di();
$di->setShared('escaper', \Phalcon\Html\Escaper::class);
\Phalcon\Di\Di::setDefault($di);
\Phalcon\Tag::setDI($di);

echo \Phalcon\Tag::textField(['name' => 'email']);
Defensive patterns

Strategy: validation

Validate before calling

use Phalcon\Di\Di;
use Phalcon\Html\Escaper;
use Phalcon\Tag;

if (null === Di::getDefault()) {
    $di = new Di();
    $di->setShared('escaper', Escaper::class);
    Di::setDefault($di);
    Tag::setDI($di);
}

echo Tag::textField(['name' => 'email']);

Type guard

function hasTagEscaperContainer(): bool
{
    return null !== \Phalcon\Di\Di::getDefault()
        || null !== \Phalcon\Tag::getDI(); // cached static check
}

Try / catch

try {
    echo \Phalcon\Tag::textField(['name' => 'email']);
} catch (\Phalcon\Tag\Exception $e) {
    if (str_contains($e->getMessage(), "'escaper' service")) {
        // container missing: escape manually and continue
        echo '<input type="text" name="' . htmlspecialchars('email', ENT_QUOTES) . '">';
    } else {
        throw $e;
    }
}

Prevention

When it happens

Trigger: Calling Tag::textField([...]) / Tag::renderAttributes() / any tag helper in a PHPUnit test or CLI script with no container; after Di::reset() in test teardown; note the static escaperService cache makes later calls succeed, producing order-dependent failures.

Common situations: Unit tests rendering tags in isolation; queue workers or CLI tools that include view partials; forgetting to set the DI container (Di::setDefault or Tag::setDI) in a custom bootstrap.

Related errors


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