doctrine/orm · error · Doctrine\ORM\Internal\Hydration\HydrationException

hydrateRowData() not implemented by this hydrator.

Error message

hydrateRowData() not implemented by this hydrator.

What it means

HydrationException from AbstractHydrator::hydrateRowData() (src/Internal/Hydration/AbstractHydrator.php:235). This is the default, unimplemented template method. Row-by-row hydration (used by AbstractQuery::toIterable()) needs hydrateRowData(); hydrators that only implement hydrateAllData() — such as SingleScalarHydrator and ScalarColumnHydrator — cannot stream rows and hit this stub.

Source

Thrown at src/Internal/Hydration/AbstractHydrator.php:235

    }

    protected function cleanupAfterRowIteration(): void
    {
    }

    /**
     * Hydrates a single row from the current statement instance.
     *
     * Template method.
     *
     * @param mixed[] $row    The row data.
     * @param mixed[] $result The result to fill.
     *
     * @throws HydrationException
     */
    protected function hydrateRowData(array $row, array &$result): void
    {
        throw new HydrationException('hydrateRowData() not implemented by this hydrator.');
    }

    /**
     * Hydrates all rows from the current statement instance at once.
     */
    abstract protected function hydrateAllData(): mixed;

    /**
     * Processes a row of the result set.
     *
     * Used for identity-based hydration (HYDRATE_OBJECT and HYDRATE_ARRAY).
     * Puts the elements of a result row into a new array, grouped by the dql alias
     * they belong to. The column names in the result set are mapped to their
     * field names during this procedure as well as any necessary conversions on
     * the values applied. Scalar values are kept in a specific key 'scalars'.
     *
     * @param mixed[] $data SQL Result Row.
     * @phpstan-param array<string, string> $id                 Dql-Alias => ID-Hash.

View on GitHub (pinned to d9b9ff7301)

Solutions

  1. Switch the iteration to a supported mode: toIterable($params, Query::HYDRATE_ARRAY), HYDRATE_OBJECT or HYDRATE_SCALAR.
  2. For single-column streams, iterate HYDRATE_SCALAR and take the first value of each row, or loop over the plain getSingleColumnResult() array if the data set fits memory.
  3. For a custom hydrator used with toIterable(), implement protected function hydrateRowData(array $row, array &$result): void.
  4. If you truly need one scalar per row streamed, use a DBAL statement ($conn->prepare(...)->executeQuery()->iterateColumn()) instead of the ORM hydrator.

Example fix

// before
$query = $em->createQuery('SELECT COUNT(o) FROM App\Entity\Order o GROUP BY o.customer');
foreach ($query->toIterable([], Query::HYDRATE_SINGLE_SCALAR) as $count) { ... }
    // HydrationException: hydrateRowData() not implemented by this hydrator.

// after
foreach ($query->toIterable([], Query::HYDRATE_SCALAR) as $row) {
    $count = (int) reset($row); // first (only) column of the row
}
Defensive patterns

Strategy: validation

Validate before calling

$streamable = [
    \Doctrine\ORM\Query::HYDRATE_OBJECT,
    \Doctrine\ORM\Query::HYDRATE_ARRAY,
    \Doctrine\ORM\Query::HYDRATE_SCALAR,
];
if (! in_array($mode, $streamable, true)) {
    throw new InvalidArgumentException("Hydration mode {$mode} cannot be used with toIterable()");
}
foreach ($query->toIterable($params, $mode) as $row) { /* ... */ }

Type guard

/** @param int|string $mode AbstractQuery::HYDRATE_* */
function isIterableHydrationMode(int|string $mode): bool
{
    return in_array($mode, [
        \Doctrine\ORM\Query::HYDRATE_OBJECT,
        \Doctrine\ORM\Query::HYDRATE_ARRAY,
        \Doctrine\ORM\Query::HYDRATE_SCALAR,
    ], true);
}

Prevention

When it happens

Trigger: Calling $query->toIterable($params, $hydrationMode) with Query::HYDRATE_SINGLE_SCALAR or Query::HYDRATE_SCALAR_COLUMN (or a custom hydrator registered without hydrateRowData()). ObjectHydrator, ArrayHydrator, ScalarHydrator and SimpleObjectHydrator implement the method; the scalar one-shot modes do not.

Common situations: Rewriting batch jobs from getResult()/getSingleScalarResult() to toIterable() for memory reasons and keeping the old hydration mode; custom hydrators written only for hydrateAll(); passing a hydration-mode constant as the second toIterable() argument without checking which modes stream.

Related errors


AI-assisted analysis of doctrine/orm@d9b9ff7301 (2026-08-21). Data as JSON: /api/errors/8100d343d06694a1. Report an issue: GitHub.