iterable accepts an array or a Traversable, and only one of those has a length that can be read without consuming it.
function summarise(iterable $rows): string
{
return count($rows) . ' rows'; // TypeError on a generator
}
// the trap: this works and empties the generator
is_array($rows) ? count($rows) : iterator_count($rows);
// the honest signature, from 8.1
function summarise(Countable&Traversable $rows): string
// or, from 8.2, if null is possible
function summarise((Countable&Traversable)|null $rows): string
A generator has no length until it has finished, which is the point of it rather than a gap. iterator_count is the trap because it appears to solve the problem and silently exhausts the generator, so the next loop sees nothing — a bug that presents as missing data rather than an error. Any function typed iterable that needs a count is really asking for an array and should say so.