Skip to content

Fix CsvReader::current() TypeError at EOF (with regression tests)#18

Merged
slashrsm merged 1 commit into
masterfrom
fix/csv-reader-current-eof-valid-check
Jul 23, 2026
Merged

Fix CsvReader::current() TypeError at EOF (with regression tests)#18
slashrsm merged 1 commit into
masterfrom
fix/csv-reader-current-eof-valid-check

Conversation

@slashrsm

Copy link
Copy Markdown
Contributor

Summary

Replaces / supersedes #3 with the same core fix plus a reproducible PHPUnit suite.

CsvReader::current() used do { … } while ($this->valid()) when column headers were set. SplFileObject::current() returns false at EOF. A do-while always runs its body once, so calling current() while the iterator is already invalid executed count(false) and threw:

TypeError: count(): Argument #1 ($value) must be of type Countable|array, false given

Why #3’s author could not reproduce

Simple “last line of a normal CSV” fixtures never call current() after the iterator is invalid. Foreach only calls current() when valid() is true, so the do-while body always started on a real line. Trailing newline / short last line / strict mode alone do not hit this path.

Real-world trigger (also noted on #3)

Port\Reader\OneToManyReader does:

$this->rightReader->next();
$rightRow = $this->rightReader->current(); // may be past last detail row
if ($this->rightReader->valid()) {
    $rightId = $this->getRowId($rightRow, …);
}

After the last matching detail row, next() leaves the detail CsvReader invalid, then current() is still invoked. That is exactly when the do-while blows up—so the last master row never completes. Matches the comment on #3 about OneToManyReader.

Direct calls after exhaustion (getRow(99), current() after iterator_to_array) hit the same bug.

Fix

Same idea as #3: only read a line when valid:

while ($this->valid()) {
    $line = $this->file->current();
    // …
}
return null;

Tests

Three new tests (all error with TypeError on unfixed master, pass with this patch):

  1. testCurrentAtEndOfFileWithHeadersReturnsNull — call current() after exhausting the reader
  2. testGetRowPastEndWithHeadersReturnsNullgetRow(99)
  3. testOneToManyReaderConsumesLastDetailRowWithoutError — full master/detail join through the last rows

Verified locally:

  • Without fix: 3 errors (count(false))
  • With fix: 3 tests / assertions green
  • Existing CsvReader tests still pass

Recommendation for #3

Close #3 in favor of this PR (same fix + tests + “Closes #3”). Do not merge #3 alone without tests.

Checklist

  • Bug reproduced with a failing test on master
  • Fix applied (while + valid check)
  • Tests pass with fix
  • CI green
  • Review

Not merging yet — waiting on CI/review per housekeeping guidance.

SplFileObject::current() returns false at end-of-file. The previous
do-while loop always executed its body once, so calling current() when
the iterator was already invalid ran count(false) and threw a TypeError
on PHP 8+.

That surfaces in the common OneToManyReader join path, which calls
rightReader->current() after next() past the last detail row and then
could not finish the last master row.

Switch to while ($this->valid()) before reading the line (same approach
as #3) and add regression tests, including an OneToManyReader integration
case.

Closes #3
@slashrsm
slashrsm merged commit a7c68d5 into master Jul 23, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant