Hey Jakub,

On 18.11.2025 19:38:37, Jakub Zelenka wrote:
Hello,

I would like to introduce a new stream error handling RFC that is part of my stream evolution work (PHP Foundation project funded by Sovereign Tech Fund) :

https://wiki.php.net/rfc/stream_errors

Kind regards,

Jakub


I super appreciate that you've tackled this. This has been a big wart in PHP's stream handling for a long time.


I have a couple questions:

Why have you chosen a big list of constants rather than enums?
I think we should push for enums where applicable, meaning enum StreamErrorMode, enum StreamErrorStore, enum StreamError.

You define what a terminal error is, but does it correlate to specific error codes? Like, is a given error code always terminal or not terminal? Or can some error codes sometimes be terminal or not terminal depending on context? I've tried to look at the code, but I don't quite understand it, e.g. php_plain_files_rename: When chown or chmod fail, the error is terminal. When the copying fails (did you forget to adapt php_copy_file_ctx?), that's non-terminal, though. Reading through the code, I have the feeling that operations which do not call out to other operations, which can error, are terminal. And all others are non-terminal. E.g. failing to open a file is terminal. That the copying where the file opening is part of, failed, is non-terminal. And then *additionally* some operations, which don't prevent success are also marked non-terminal - squeezing it into the boolean for the purpose of not erroring. Which makes some sense to me, but the description in the RFC is really confusing here. And the meanings are muddied.

Thus, if I understand that correctly, there should be an unique mapping from error code to terminal. If we were to go with enums, we could make it trivially a property of the individual enum values instead.

Should StreamException be attached the non-terminal errors which are caused by the terminal errors? I.e. StreamException clearly is STREAM_ERROR_CODE_PERMISSION_DENIED for example, but it happens during a copy, so, should the information that a STREAM_ERROR_CODE_COPY_FAILED was caused by that, also be present on the StreamException?

Are stream errors which happen as part of a fully wrapped functionality marked as wrapper error as well? Like if the server sent an invalid TLS response to a file_get_contents("https://...";) operation, making the whole operation fail? You obviously don't have access to the intermediary internal stream resource there, where the stream error is attached. Is this what the logged errors section is about? Is that section talking about "php_stream_display_wrapper_errors", which can concat some errors? I see that this logic is mostly retained.

Do I understand the implementation correctly, that user error handlers can be called multiple times for a same operation? E.g. when copying a file, you'll get first a PERMISSION_DENIED then a COPY_FAILED? Should these not be somehow merged? Especially with global user_error handlers, these cannot be aware of the fact that the permission denied is part of anything else - at least not until possibly a second user_error invocation happens. Also, is the internal resource used for e.g. file_get_contents() leaked into user error handlers? I'm not sure whether that's desirable. If that's intended, that's fine too, I think.

Further suggestion on stream_get_errors() - why not return a proper object instead of a loosely typed array? Proper typing, and creation is actually faster. Also "docref" is so... antiquated? maybe just "function" and have it contain the function name of the current operation.

Does stream_get_errors() ever reset? Looking at the code it doesn't seem so. It will just forever grow. E.g. fwrite() will report an error every single time when the buffer is full and a write is attempted. If errors aren't freed, that's just a memory leak (as long as the socket lives). This is a serious flaw.

Lastly, stream_get_errors() is overkill in a lot of cases (especially when using fread()/fwrite() on simple sockets). I just care about the last error code (there's just going to be one anyway). I'd love having a stream_get_last_error() returning just the code.


Overall, I like the direction of the RFC, but I'd like to see improved handling for "terminal error inside non-terminal operation having its own error" in particular. Enums are rather a suggestion, not a requirement, but they would be in my opinion very fitting here.


Thanks,

Bob

Reply via email to