Typing a parameter iterable and then needing its length

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.