Hi internals,
I'd like to open the discussion on an RFC proposing a dedicated CSV
extension (ext/csv) for PHP core.
RFC: https://wiki.php.net/rfc/csv_extension
Implementation: https://github.com/php/php-src/pull/24199
The proposal builds upon Gina Peter Banyard's girgias/csv extension
(BSD-3-Clause), with additional functionality for file and stream handling.
The motivation includes the deprecation of the SplFileObject CSV methods in
PHP 8.6, which leaves certain use cases without a direct replacement.
The proposed extension provides:
Six functions and one class in the Csv\ namespace.
RFC 4180-compatible parsing and serialization by default.
Locale-independent processing without the proprietary escape mechanism.
Multibyte delimiters, enclosures, and EOL sequences.
Strict and lax collection parsing.
Lazy iteration over CSV files and streaming output.
The implementation includes PHPT tests covering parsing, serialization,
stream handling, resource lifetime, and various edge cases.
The pull request's CI checks are currently passing across the tested
platforms, including debug, ZTS, and AddressSanitizer configurations.
The initial API is deliberately kept small. A more comprehensive
object-oriented Reader/Writer API, including capability-oriented
interfaces, is left for potential future discussion.
I'd particularly appreciate feedback on the public API design, the naming
of LazyLaxCollection, and whether the extension should be always enabled or
optional at build time.
For transparency, Gina has not reviewed or endorsed this RFC. Parts of the
implementation were developed with LLM assistance; I have reviewed the code
and take full responsibility for the contribution.
This is an invitation for discussion, not a call for votes.
Thanks in advance for your feedback.
Best regards,
Damian Jóźwiak
On Fri, Oct 9, 2026 at 8:22 AM Damian Jóźwiak damian.jozwiak.lodz@gmail.com
wrote:
Hi internals,
I'd like to open the discussion on an RFC proposing a dedicated CSV
extension (ext/csv) for PHP core.RFC: https://wiki.php.net/rfc/csv_extension
Implementation: https://github.com/php/php-src/pull/24199
The proposal builds upon Gina Peter Banyard's girgias/csv extension
(BSD-3-Clause), with additional functionality for file and stream handling.The motivation includes the deprecation of the SplFileObject CSV methods
in PHP 8.6, which leaves certain use cases without a direct replacement.The proposed extension provides:
Six functions and one class in the Csv\ namespace.
RFC 4180-compatible parsing and serialization by default.
Locale-independent processing without the proprietary escape mechanism.
Multibyte delimiters, enclosures, and EOL sequences.
Strict and lax collection parsing.
Lazy iteration over CSV files and streaming output.
The implementation includes PHPT tests covering parsing, serialization,
stream handling, resource lifetime, and various edge cases.The pull request's CI checks are currently passing across the tested
platforms, including debug, ZTS, and AddressSanitizer configurations.The initial API is deliberately kept small. A more comprehensive
object-oriented Reader/Writer API, including capability-oriented
interfaces, is left for potential future discussion.I'd particularly appreciate feedback on the public API design, the naming
of LazyLaxCollection, and whether the extension should be always enabled
or optional at build time.For transparency, Gina has not reviewed or endorsed this RFC. Parts of the
implementation were developed with LLM assistance; I have reviewed the code
and take full responsibility for the contribution.This is an invitation for discussion, not a call for votes.
Thanks in advance for your feedback.
Best regards,
Damian Jóźwiak
I see that there's no support for open streams/resources, would that be
something worth adding? I don't want to have to write things to a file
before I can start processing them if I already have an open stream. Same
goes for reading, I don't want to have to read everything before passing it
somewhere, often having it as a resource is more useful to me.
About LazyLaxCollection, what does Lax mean here?
Hi,
Thanks for the feedback! Both are good points.
On streams/resources:
I agree that supporting already-open streams would be a valuable addition,
particularly for reading. For writing, it is already possible to stream
rows individually without buffering the entire collection:
foreach ($rows as $row) {
fwrite($stream, Csv\array_to_row($row));
}
However, a dedicated collection_to_stream() would provide a more convenient
API, consistent row-width validation, and symmetry with the existing
file-based function. For reading, the gap is more significant. Splitting a
stream into lines and passing them to row_to_array() is not reliable
because enclosed CSV fields may contain line breaks. The parser used by
createFromFile() already operates on PHP streams internally, so exposing
this functionality for existing stream resources should be relatively
straightforward. I'm considering two additional entry points:
Csv\collection_to_stream($stream, iterable $collection, ...): void
Csv\LazyLaxCollection::createFromStream($stream, ...): LazyLaxCollection
I would prefer separate entry points rather than overloading the existing
$file parameter to accept both paths and resources. Since PHP does not
support resource as a declared parameter type, the stream parameter would
be untyped, documented as @param resource, and validated at runtime, with
invalid arguments resulting in a TypeError.
The intended semantics for createFromStream() would be:
- Parsing starts at the stream's current position, allowing callers to skip
a BOM or preamble beforehand. - The extension borrows the stream and never closes it.
- Repeated iteration seeks back to the position recorded when the
collection was created. Non-seekable streams support only a single
iteration. - If the caller closes the stream during iteration, subsequent reads should
throw an error rather than access freed memory. - Since the parser reads ahead in chunks, the underlying stream's position
after partial iteration is not guaranteed to match the end of the last
returned row. Mixing reads from the collection with direct reads from the
same stream would therefore not be supported.
Would these semantics work for your use cases, particularly the read-ahead
behavior and separate entry points?
On LazyLaxCollection:
"Lax" means that rows are not required to contain the same number of
fields. This mirrors the distinction between buffer_to_collection(), which
throws a ValueError when row widths differ, and buffer_to_collection_lax(),
which accepts rows of varying widths. The name comes from the original
girgias/csv implementation by Gina Peter Banyard. To make this behavior
explicit, I've also added a dedicated PHPT test:
ext/csv/tests/LazyCollection/fromBuffer/lax_varying_field_count.phpt. The
test verifies that LazyLaxCollection::createFromBuffer() accepts
consecutive rows containing 3, 1, 2, and 4 fields, while
buffer_to_collection() throws a ValueError for the same input. Naming is
already listed as an open issue in the RFC, along with whether a strict
lazy variant should be included. Your question reinforces that the current
name may not communicate its purpose clearly enough. I'd welcome
suggestions here. For example, LazyCollection with configurable strictness
could be worth considering. I'll update the RFC once we've had a chance to
discuss the API details.
Thanks again!
Best regards,
Damian
On Fri, Oct 9, 2026 at 8:22 AM Damian Jóźwiak <
damian.jozwiak.lodz@gmail.com> wrote:Hi internals,
I'd like to open the discussion on an RFC proposing a dedicated CSV
extension (ext/csv) for PHP core.RFC: https://wiki.php.net/rfc/csv_extension
Implementation: https://github.com/php/php-src/pull/24199
The proposal builds upon Gina Peter Banyard's girgias/csv extension
(BSD-3-Clause), with additional functionality for file and stream handling.The motivation includes the deprecation of the SplFileObject CSV methods
in PHP 8.6, which leaves certain use cases without a direct replacement.The proposed extension provides:
Six functions and one class in the Csv\ namespace.
RFC 4180-compatible parsing and serialization by default.
Locale-independent processing without the proprietary escape
mechanism.Multibyte delimiters, enclosures, and EOL sequences.
Strict and lax collection parsing.
Lazy iteration over CSV files and streaming output.
The implementation includes PHPT tests covering parsing, serialization,
stream handling, resource lifetime, and various edge cases.The pull request's CI checks are currently passing across the tested
platforms, including debug, ZTS, and AddressSanitizer configurations.The initial API is deliberately kept small. A more comprehensive
object-oriented Reader/Writer API, including capability-oriented
interfaces, is left for potential future discussion.I'd particularly appreciate feedback on the public API design, the naming
of LazyLaxCollection, and whether the extension should be always enabled
or optional at build time.For transparency, Gina has not reviewed or endorsed this RFC. Parts of
the implementation were developed with LLM assistance; I have reviewed the
code and take full responsibility for the contribution.This is an invitation for discussion, not a call for votes.
Thanks in advance for your feedback.
Best regards,
Damian JóźwiakI see that there's no support for open streams/resources, would that be
something worth adding? I don't want to have to write things to a file
before I can start processing them if I already have an open stream. Same
goes for reading, I don't want to have to read everything before passing it
somewhere, often having it as a resource is more useful to me.About LazyLaxCollection, what does Lax mean here?
This RFC led me to take a closer look at the built-in CSV functions, and
what I found makes me favor a different path to the same goals.
The RFC argues that their behavior "cannot be fixed without breaking
compatibility". I went through the list:
-
$escape: already addressed by the PHP 8.4 deprecation; the default
changes to "" in PHP 9.0. -
Locale dependence: php/php-src#24207 (
https://github.com/php/php-src/pull/24207, under review, prompted by this
RFC) removes the per-byte libc lookup for ASCII characters in
ASCII-compatible locales. The remaining isspace() before an opening
enclosure can be replaced with an explicit space/tab check. For non-ASCII
bytes in CJK locales, mbrlen() serves a purpose: a Shift_JIS character can
contain a '|' trail byte without that byte being a delimiter. A
locale-independent byte parser would split it. -
Single-byte delimiters and enclosures: longer values currently throw
ValueError, so support for multibyte tokens can be added without changing
existing valid calls. -
No inverse of
str_getcsv(): a str_putcsv() function would fill that gap
and let SplFileObject::fputcsv() callers use
$file->fwrite(str_putcsv($row)). -
[null] for an empty line and whitespace before an enclosure: these can be
addressed by an opt-in strict mode without changing the default behavior.
Enclosing fields containing spaces on output is permitted by RFC 4180
section 2.5 and is not a compliance defect.
Strict validation, multibyte tokens, string serialization and lazy reading
can all be built on the existing API. Lazy iteration over a stream already
works with a generator around fgetcsv(); strict row-width validation can be
layered on top. These changes can be developed and reviewed independently,
without replacing the parser or breaking existing calls.
I have benchmarks and test scripts for the existing implementation and the
proposed extension here:
https://github.com/ArtUkrainskiy/php-src-bench/tree/main/reports/csv-pr-24199
Adding ext/csv instead would leave core maintaining two independent CSV
parsers for the foreseeable future. The RFC explicitly leaves the built-ins
unchanged, and the June discussion around the SplFileObject deprecations
showed understandable reluctance to remove existing APIs without an
established migration path (https://news-web.php.net/php.internals/131503,
and the August follow-up: https://news-web.php.net/php.internals/132186).
The branch itself is in good shape. The issues I reported on the PR were
addressed quickly, and the parser is now faster than fgetcsv() in my
benchmarks. This is not an objection to the implementation or the work that
went into it.
My concern is the long-term maintenance cost of introducing a second parser
when the existing one can be improved incrementally. I'd rather help land
those improvements in ext/standard.
пт, 9 окт. 2026 г. в 17:07, Damian Jóźwiak damian.jozwiak.lodz@gmail.com:
Hi internals,
I'd like to open the discussion on an RFC proposing a dedicated CSV
extension (ext/csv) for PHP core.RFC: https://wiki.php.net/rfc/csv_extension
Implementation: https://github.com/php/php-src/pull/24199
The proposal builds upon Gina Peter Banyard's girgias/csv extension
(BSD-3-Clause), with additional functionality for file and stream handling.The motivation includes the deprecation of the SplFileObject CSV methods
in PHP 8.6, which leaves certain use cases without a direct replacement.The proposed extension provides:
Six functions and one class in the Csv\ namespace.
RFC 4180-compatible parsing and serialization by default.
Locale-independent processing without the proprietary escape mechanism.
Multibyte delimiters, enclosures, and EOL sequences.
Strict and lax collection parsing.
Lazy iteration over CSV files and streaming output.
The implementation includes PHPT tests covering parsing, serialization,
stream handling, resource lifetime, and various edge cases.The pull request's CI checks are currently passing across the tested
platforms, including debug, ZTS, and AddressSanitizer configurations.The initial API is deliberately kept small. A more comprehensive
object-oriented Reader/Writer API, including capability-oriented
interfaces, is left for potential future discussion.I'd particularly appreciate feedback on the public API design, the naming
of LazyLaxCollection, and whether the extension should be always enabled
or optional at build time.For transparency, Gina has not reviewed or endorsed this RFC. Parts of the
implementation were developed with LLM assistance; I have reviewed the code
and take full responsibility for the contribution.This is an invitation for discussion, not a call for votes.
Thanks in advance for your feedback.
Best regards,
Damian Jóźwiak
--
Best regards,
Artem Ukrainskiy
Hi everyone,
Thank you for the feedback, and Artem, for the detailed review and
independent verification of the implementation.
I think the discussion has raised two important questions: whether these
improvements belong in the existing CSV API or a dedicated extension, and
what an API supporting both files and already-open streams should look like.
Artem, your point about incremental improvements is fair. The RFC's
statement that the existing behaviour “cannot be fixed without breaking
compatibility” is too broad. Several improvements could be introduced
through additional functions or opt-in behaviour while preserving existing
calls. You are also right that enclosing fields containing spaces is
permitted by RFC 4180, and that locale-independent byte parsing has
different implications for encodings such as Shift_JIS.
At the same time, PHP has taken the approach of introducing a modern API
alongside an established one when their contracts differ substantially. The
new DOM API, including Dom\Document, coexists with the legacy DOMDocument
API, and the URI extension provides a dedicated API alongside parse_url().
These are relevant precedents for giving users clearer semantics without
changing the behaviour of existing applications. Of course, those
precedents do not automatically justify another CSV parser. They do suggest
that improving the existing API and introducing a dedicated one are both
reasonable approaches to evaluate. The question is whether the consistency
and usability of the proposed CSV API justify its maintenance cost.
My motivation is to provide parsing, serialization, validation and file
handling with consistent, explicit defaults. In particular, the file-based
entry points accept a path and manage the underlying stream, covering the
convenience previously provided by SplFileObject's CSV methods. The
existing fgetcsv()/fputcsv() functions operate on streams supplied by the
caller. Both use cases deserve support.
Separately, the remaining overlapping-token writer issue reported on the PR
has been addressed in
https://github.com/php/php-src/commit/b7866156dc7e77304fe3419edceba6f890b113cb.
Fields are now enclosed when tokens could form across field/separator
boundaries. Distinguishable overlapping dialects remain supported;
ambiguous combinations such as delimiter xy with enclosure xyx are
rejected by both readers and writers. Regression tests cover both
behaviours, and all 92 PHPT tests pass in my local verification.
With those concerns in mind, I would like to discuss three possible
directions:
A. Improve the existing API in ext/standard, adding capabilities such as
string serialization, multibyte tokens and opt-in strict parsing, with file
and lazy-reading conveniences considered where needed.
B. Continue with the dedicated ext/csv proposal, retaining its
function-based API and lazy collection, while adding entry points for
existing streams and resolving the naming questions.
C. Rework the proposal around an object-oriented API in the Csv namespace,
with explicit file and stream factories and writing methods. Stream
ownership, iteration, validation and string conversion would need to be
specified before this became a concrete voting option.
My preference remains a dedicated API with consistent semantics, but I am
open to revising its shape or pursuing the existing-API route if that is
the direction the project prefers.
Would a directional vote between these approaches be useful if discussion
does not establish a clear preference? I would first document the
alternatives and their trade-offs, and agree on the voting format.
Selecting a direction would guide a revised proposal; it would not approve
an API whose details have yet to be reviewed.
The current RFC does not propose deprecating the existing CSV functions.
Any future consolidation would need its own discussion and an established
migration path, rather than assuming that introducing a new API commits PHP
to removing the old one.
The goal is to establish a maintainable CSV path for PHP users, including
those migrating from the deprecated SplFileObject methods. I would welcome
your thoughts on which direction best serves that goal.
Best regards,
Damian
On Sat, Oct 10, 2026 at 12:38 PM Artem Ukrainskiy art.ukrainskiy@gmail.com
wrote:
This RFC led me to take a closer look at the built-in CSV functions, and
what I found makes me favor a different path to the same goals.The RFC argues that their behavior "cannot be fixed without breaking
compatibility". I went through the list:
$escape: already addressed by the PHP 8.4 deprecation; the default
changes to "" in PHP 9.0.Locale dependence: php/php-src#24207 (
https://github.com/php/php-src/pull/24207, under review, prompted by this
RFC) removes the per-byte libc lookup for ASCII characters in
ASCII-compatible locales. The remaining isspace() before an opening
enclosure can be replaced with an explicit space/tab check. For non-ASCII
bytes in CJK locales, mbrlen() serves a purpose: a Shift_JIS character can
contain a '|' trail byte without that byte being a delimiter. A
locale-independent byte parser would split it.Single-byte delimiters and enclosures: longer values currently throw
ValueError, so support for multibyte tokens can be added without changing
existing valid calls.No inverse of
str_getcsv(): a str_putcsv() function would fill that gap
and let SplFileObject::fputcsv() callers use
$file->fwrite(str_putcsv($row)).[null] for an empty line and whitespace before an enclosure: these can
be addressed by an opt-in strict mode without changing the default
behavior. Enclosing fields containing spaces on output is permitted by RFC
4180 section 2.5 and is not a compliance defect.Strict validation, multibyte tokens, string serialization and lazy reading
can all be built on the existing API. Lazy iteration over a stream already
works with a generator aroundfgetcsv(); strict row-width validation can be
layered on top. These changes can be developed and reviewed independently,
without replacing the parser or breaking existing calls.I have benchmarks and test scripts for the existing implementation and the
proposed extension here:
https://github.com/ArtUkrainskiy/php-src-bench/tree/main/reports/csv-pr-24199Adding ext/csv instead would leave core maintaining two independent CSV
parsers for the foreseeable future. The RFC explicitly leaves the built-ins
unchanged, and the June discussion around the SplFileObject deprecations
showed understandable reluctance to remove existing APIs without an
established migration path (https://news-web.php.net/php.internals/131503,
and the August follow-up: https://news-web.php.net/php.internals/132186).The branch itself is in good shape. The issues I reported on the PR were
addressed quickly, and the parser is now faster thanfgetcsv()in my
benchmarks. This is not an objection to the implementation or the work that
went into it.My concern is the long-term maintenance cost of introducing a second
parser when the existing one can be improved incrementally. I'd rather help
land those improvements in ext/standard.пт, 9 окт. 2026 г. в 17:07, Damian Jóźwiak <damian.jozwiak.lodz@gmail.com
:
Hi internals,
I'd like to open the discussion on an RFC proposing a dedicated CSV
extension (ext/csv) for PHP core.RFC: https://wiki.php.net/rfc/csv_extension
Implementation: https://github.com/php/php-src/pull/24199
The proposal builds upon Gina Peter Banyard's girgias/csv extension
(BSD-3-Clause), with additional functionality for file and stream handling.The motivation includes the deprecation of the SplFileObject CSV methods
in PHP 8.6, which leaves certain use cases without a direct replacement.The proposed extension provides:
Six functions and one class in the Csv\ namespace.
RFC 4180-compatible parsing and serialization by default.
Locale-independent processing without the proprietary escape
mechanism.Multibyte delimiters, enclosures, and EOL sequences.
Strict and lax collection parsing.
Lazy iteration over CSV files and streaming output.
The implementation includes PHPT tests covering parsing, serialization,
stream handling, resource lifetime, and various edge cases.The pull request's CI checks are currently passing across the tested
platforms, including debug, ZTS, and AddressSanitizer configurations.The initial API is deliberately kept small. A more comprehensive
object-oriented Reader/Writer API, including capability-oriented
interfaces, is left for potential future discussion.I'd particularly appreciate feedback on the public API design, the naming
of LazyLaxCollection, and whether the extension should be always enabled
or optional at build time.For transparency, Gina has not reviewed or endorsed this RFC. Parts of
the implementation were developed with LLM assistance; I have reviewed the
code and take full responsibility for the contribution.This is an invitation for discussion, not a call for votes.
Thanks in advance for your feedback.
Best regards,
Damian Jóźwiak--
Best regards,
Artem Ukrainskiy