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
- Switch the iteration to a supported mode: toIterable($params, Query::HYDRATE_ARRAY), HYDRATE_OBJECT or HYDRATE_SCALAR.
- 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.
- For a custom hydrator used with toIterable(), implement protected function hydrateRowData(array $row, array &$result): void.
- 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
- Bookmark that toIterable() supports OBJECT, ARRAY and SCALAR modes only.
- For custom hydrators used with toIterable(), implement hydrateRowData() (and hydrateAllData()).
- Use DBAL iterateColumn()/iterateAssociative() for streaming scalar data.
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
- Uninitialized result set mapping.
- {argAlias} does not exist
- Attempting to change readonly property %s::$%s.
- Unable to use access strategy type of [%s] without a Concurr
- Unrecognized access strategy type [%s]
AI-assisted analysis of doctrine/orm@d9b9ff7301 (2026-08-21).
Data as JSON: /api/errors/8100d343d06694a1.
Report an issue: GitHub.