phalcon/cphalcon · error · Phalcon\ADR\Exceptions\ActionDirectoryNotSet

No action directory set; call setActionDirectory().

Error message

No action directory set; call setActionDirectory().

What it means

QueryBuilderCursor throws InvalidBuilderInstance when the 'builder' config value exists but is not an instance of Phalcon\Mvc\Model\Query\Builder. A raw SQL string, a Phalcon\Mvc\Model\Query\Query object, a Resultset, or an array query definition all fail this instanceof check.

Source

Thrown at phalcon/ADR/Router/Router.zep:147

        if empty segments {
            return this->baseNamespace . "\\" . verb;
        }

        for segment in segments {
            let parts[] = this->camelize(segment);
        }

        return this->baseNamespace
            . "\\" . implode("\\", parts)
            . "\\" . verb . implode("", parts);
    }

    public function match(<RequestInterface> request) -> <RouterMatchInterface> | null
    {
        var path, method, located, other;

        if this->actionDirectory === "" {
            throw new ActionDirectoryNotSet();
        }

        let path   = request->getURI(true),
            method = request->getMethod();

        let located = this->locate(method, path);
        if typeof located == "array" {
            return new RouterMatch(
                located[0],
                located[1],
                this->middlewareFor(located[0])
            );
        }

        for other in this->verbs() {
            if strcasecmp(other, method) !== 0 && typeof this->locate(other, path) == "array" {
                throw new MethodNotAllowed();
            }

View on GitHub (pinned to b7419de9cd)

Solutions

  1. Pass the Builder object itself — the value you call getQuery()/execute() on must NOT be pre-compiled.
  2. Build one with $modelsManager->createBuilder()->from(Model::class)->where(...) or new Builder(['models' => Model::class, ...]).
  3. If you only have PHQL text, wrap it: new Builder(['phql' => $phql]) produces a Builder.

Example fix

// before
$query = $builder->getQuery();
$paginator = new QueryBuilderCursor(
    [
        'limit'        => 20,
        'builder'      => $query, // Query object, not Builder
        'cursorColumn' => 'id',
    ]
); // throws InvalidBuilderInstance

// after
$paginator = new QueryBuilderCursor(
    [
        'limit'        => 20,
        'builder'      => $builder, // the Builder itself
        'cursorColumn' => 'id',
    ]
);
Defensive patterns

Strategy: type-guard

Type guard

function acceptsQueryBuilder(mixed $value): bool
{
    return $value instanceof \Phalcon\Mvc\Model\Query\Builder;
}

// usage
if (!acceptsQueryBuilder($config['builder'])) {
    throw new \InvalidArgumentException(
        'Expected Phalcon\\Mvc\\Model\\Query\\Builder, got '
        . get_debug_type($config['builder'])
    );
}

Prevention

When it happens

Trigger: Passing 'builder' => 'SELECT * FROM orders' (a PHQL/SQL string), an already-compiled Query object from getQuery(), or a resultset instead of the Builder itself.

Common situations: Confusing the Builder (fluent query definition) with the compiled Query; passing getQuery() output by mistake; porting code from components that accept query strings.

Related errors


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