yiisoft/yii2 · error · yii\base\InvalidArgumentException

Subquery for EXISTS operator must be a Query object.

Error message

Subquery for EXISTS operator must be a Query object.

What it means

yii\db\conditions\ExistsCondition::fromArrayDefinition() (framework/db/conditions/ExistsCondition.php:51) builds ['exists', X] / ['not exists', X] conditions and requires operands[0] to be an instance of yii\db\Query. The EXISTS operators express SQL EXISTS (subquery), so Yii rejects anything that is not a query object — raw SQL string, hash array, or null — with InvalidArgumentException before any SQL is generated.

Source

Thrown at framework/db/conditions/ExistsCondition.php:51

    /**
     * ExistsCondition constructor.
     *
     * @param string $operator the operator to use (e.g. `EXISTS` or `NOT EXISTS`)
     * @param Query $query the [[Query]] object representing the sub-query.
     */
    public function __construct($operator, $query)
    {
        $this->operator = $operator;
        $this->query = $query;
    }

    /**
     * {@inheritdoc}
     */
    public static function fromArrayDefinition($operator, $operands)
    {
        if (!isset($operands[0]) || !$operands[0] instanceof Query) {
            throw new InvalidArgumentException('Subquery for EXISTS operator must be a Query object.');
        }

        return new static($operator, $operands[0]);
    }

    /**
     * @return string
     */
    public function getOperator()
    {
        return $this->operator;
    }

    /**
     * @return Query
     */
    public function getQuery()
    {

View on GitHub (pinned to 66f00d18a2)

Solutions

  1. Wrap the subquery: ->andWhere(['exists', (new \yii\db\Query())->select('1')->from('{{%order}} o')->where('o.customer_id = c.id')])
  2. For negation use ['not exists', $subQuery]
  3. If raw SQL is required, use new \yii\db\Expression('EXISTS (SELECT ...)') with explicit params instead of the array operator

Example fix

// before
$query->andWhere(['exists', 'SELECT 1 FROM `order` o WHERE o.customer_id = c.id']); // string -> InvalidArgumentException

// after
$query->andWhere(['exists', (new \yii\db\Query())
    ->select('1')
    ->from('{{%order}} o')
    ->where('o.customer_id = c.id'),
]);
Defensive patterns

Strategy: type-guard

Type guard

function isQueryObject($value): bool
{
    return $value instanceof \yii\db\Query;
}

if (isQueryObject($subQuery)) {
    $query->andWhere(['not exists', $subQuery]);
}

Try / catch

try {
    $rows = $query->all();
} catch (\yii\base\InvalidArgumentException $e) {
    // subquery was not a Query object — rebuild with new Query()
    \Yii::error($e->getMessage(), __METHOD__);
}

Prevention

When it happens

Trigger: ->where(['exists', 'SELECT id FROM orders']) (raw SQL string); ['not exists', ['id' => 1]] (hash array used as subquery); ['exists', null] when the subquery variable failed to build; passing the result of ->one() instead of the query object itself.

Common situations: Converting hand-written SQL with EXISTS subqueries to the query builder; forgetting to wrap the subquery in (new \yii\db\Query())->from(...); passing an ActiveRecord row instead of the ActiveQuery. ActiveQuery extends Query, so active-record subqueries are valid.

Related errors


AI-assisted analysis of yiisoft/yii2@66f00d18a2 (2026-08-17). Data as JSON: /api/errors/54d55914279cf846. Report an issue: GitHub.