phpstorm.dev

PHP ecosystem insights

PHP knowledge

Versioned PHP knowledge with primary sources and explicit verification dates.

Source checks through 2026-10-14 · 56 sources

Verified 2026-10-12 discussion Permanent entry

CSV extension (ext/csv) proposal

Under discussion; not accepted. The RFC page (version 1.0, dated 2026-10-08) was created as a Draft at 17:06 UTC on 8 October, the same day the author asked for RFC karma and got it. After an edit at 06:16 UTC on 9 October the status line reads Under Discussion. At 06:19 UTC the author opened the discussion on internals, calling it "an invitation for discussion, not a call for votes". The official RFC index collected at 10:01 UTC on 9 October lists it as discussion. At 19:43 UTC on 9 October the author published version 1.1 (dated 2026-10-09). Its changelog says it was updated after a review of the implementation (see below). An edit at 11:30 UTC on 11 October made it version 1.2 (dated 2026-10-11), still Under Discussion. The page's discussion and voting thread fields still read "tbd". The author is Damian Jóźwiak. The proposed version is the "next minor PHP version (the release after PHP 8.6)", recorded here as 8.7.

The proposal is based on Gina Peter Banyard's girgias/csv extension (BSD-3-Clause). The RFC says Gina has not reviewed it and that it is not her endorsement. The announcement adds that parts of the implementation were developed with LLM assistance and reviewed by the author.

Motivation given by the RFC

The RFC argues that the existing CSV functions (fgetcsv(), fputcsv(), str_getcsv() and the SplFileObject CSV methods) cannot follow RFC 4180 without breaking compatibility. It names the proprietary $escape mechanism, locale-dependent parsing, single-byte delimiters and enclosures, empty lines parsing as [null], and the lack of an inverse of str_getcsv(). It also says PHP 8.6 deprecated SplFileObject::fgetcsv(), fputcsv(), setCsvControl() and getCsvControl() with no replacement. The PHP 8.6 deprecations RFC does list "Deprecate SplFileObject CSV methods", but the result of that vote was not checked here.

What is proposed

A new, always-enabled ext/csv with six functions and one class in the Csv\ namespace:

Every function takes $delimiter = ',', $enclosure = '"' and $eolSequence = "\r\n". All three may be multibyte. Parsing is described as binary-safe and locale-independent, with no escape character. Invalid dialects and malformed input throw ValueError, non-array rows throw TypeError, I/O failures throw Error, and no function returns false.

Version 1.1 adds a "Strict parsing" section. Whatever the dialect, input that does not follow the RFC 4180 record grammar throws ValueError instead of being guessed. This covers an enclosure inside an unenclosed field (including whitespace or a BOM before the opening enclosure), data between a closing enclosure and the next delimiter or line ending ("a" ,b), an enclosure that is never closed, and CR or LF outside an enclosed field that is not part of the EOL sequence. As a result, an LF-only file read with the default "\r\n" is an error, not one long row; it is read by passing "\n". row_to_array() now accepts exactly one row, with an optional trailing EOL sequence.

Version 1.1 also describes stream handling in more detail. $file accepts anything the streams layer accepts (paths, php://, compress.zlib://, user wrappers) and is opened with the default stream context. A collection from createFromFile() owns its stream like SplFileObject does: fclose() refuses to close it, and it is closed when the object is destroyed. Iteration state belongs to each iterator, so nested loops over a buffer-backed collection are independent. A second concurrent loop over a file-backed collection throws Error.

The RFC lists differences from the legacy functions: no escape character, no [null] for empty lines, an enclosure inside an unenclosed field, data after a closing enclosure and an unterminated enclosure are errors, whitespace before an enclosure is not silently dropped, and the default line ending is "\r\n". (This page's earlier summary of version 1.0 said whitespace before an enclosure is kept; under 1.1's strict parsing it is an error. The 1.0 wording could not be re-read, because the wiki's revision history requires a login.) Existing functions keep their behavior. The only backward-compatibility impact named is that the top-level Csv\ namespace becomes reserved.

Version 1.1 listed as an open issue that dialects in which one token is a prefix of another (for example delimiter :: with EOL :) are ambiguous and could be rejected during argument validation. Version 1.2 replaces that with a "Multibyte and overlapping dialect tokens" section in the proposal. Overlapping tokens are allowed where rows still round-trip: writers also enclose a field when a token could form across the boundary with a neighbouring delimiter or EOL sequence (the RFC's example: delimiter --, enclosure aa). Combinations that cannot represent every row unambiguously throw ValueError in readers and writers, for example delimiter xy with enclosure xyx. The remaining open issues are whether the extension can be disabled at build time (the implementation supports --disable-csv; the RFC proposes always enabled), the name LazyLaxCollection, and whether a strict lazy variant should ship in the first version.

Version 1.2 (whether these parts are new since 1.1 was not checked) names two gaps left by the PHP 8.6 SplFileObject deprecation, raised during that discussion: SplFileObject::fputcsv() has no drop-in replacement because fputcsv() needs a stream that SplFileObject does not expose, and SplFileObject::READ_CSV was not deprecated although setCsvControl(), the only way to configure its dialect, was. Its "Unaffected PHP Functionality" section says the RFC neither changes nor deprecates fgetcsv(), fputcsv(), str_getcsv() or the SplFileObject CSV methods. The future scope includes a deprecation timeline for str_getcsv(), fputcsv() and fgetcsv() ("deliberately not part of this RFC"), a separate RFC to deprecate SplFileObject::READ_CSV, and header/encoding handling. As checked on 9 and 11 October, it also lists a possible object-oriented Csv\Reader/Csv\Writer API with capability interfaces for a later RFC. None of these is part of this RFC. The planned vote is one question needing a 2/3 majority.

Discussion so far

The first reply, from Lynn on 9 October (message 132845), asked for support for already-open streams/resources, for reading and writing, and what "Lax" means. The author replied at 08:59 UTC:

The author said the RFC will be updated after the API discussion. Neither version 1.1 nor version 1.2 (checked at 16:05 UTC on 11 October) contains collection_to_stream() or createFromStream(), so they are not part of the proposal as written.

On 10 October at 10:37 UTC Artem Ukrainskiy (GitHub user ArtUkrainskiy, whose review the RFC cites) argued for a different path: improving the existing ext/standard CSV functions incrementally instead of adding a second parser. They went through the RFC's motivation list:

Their main concern is the long-term cost of maintaining two CSV parsers, given the reluctance in the June and August discussions to remove existing APIs without a migration path. They said this is not an objection to the implementation: the issues they reported were fixed quickly and the parser is now faster than fgetcsv() in their benchmarks. They published their benchmarks and test scripts in a public GitHub repository; those benchmarks were not checked here.

The author replied at 11:50 UTC on 11 October, after the version 1.2 edit:

Implementation

The implementation is php-src PR #24199, "[RFC] Add CSV extension (ext/csv)", opened on 8 October 2026 at 17:03 UTC. On 11 October (GitHub API, about 16:05 UTC) it was still open, not a draft and not merged. The RFC calls it a port of girgias/csv 0.6.0.

Version 1.1 says a review of the PR by GitHub user ArtUkrainskiy found a use-after-free (also present in girgias/csv 0.6.0), a quadratic rescan of long rows in createFromFile() and smaller issues. The RFC says all of them are fixed, covered by tests and checked with AddressSanitizer and a differential fuzzer. It also claims six girgias/csv 0.6.0 bugs fixed during the port and 90 phpt tests. In the 11 October message the author said a remaining writer issue with overlapping tokens was fixed in a later PR commit and that all 92 phpt tests pass locally (the RFC page still says 90). On the reviewer's benchmark it reports createFromFile() reading rows slightly faster than fgetcsv() (363–1,539 ns against 402–1,742 ns per row, with the php_mblen() fast path of php-src PR #24207) and writing 10–20% slower than fputcsv(). These are the author's statements; the review reports, fixes and benchmarks were not checked here.

Limitations

This page summarises a proposal whose list discussion began on 9 October. An alternative of extending the existing functions has been proposed on the list (10 October), and the author has since asked whether the direction (A, B or C above) should be settled first (11 October). The full raw page was read on 12 October: its changelog entry for 1.2 (dated 2026-10-11) matches the overlapping-token policy above, says the ambiguous-dialect open issue is resolved and links php-src commit b7866156 for the implementation. Its "Rejected Features" section lists an object-oriented Reader/Writer API in this RFC (left to future work) and compatibility with the legacy fgetcsv() dialect (escape character, [null] rows, locale dependence). The names, behavior and target version can change, and nothing here is in released PHP. The API description comes from the RFC text and was not tested against the PR. girgias/csv can already be installed as a third-party extension (see PIE 1.5).