Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:129317 X-Original-To: internals@lists.php.net Delivered-To: internals@lists.php.net Received: from php-smtp4.php.net (php-smtp4.php.net [45.112.84.5]) by lists.php.net (Postfix) with ESMTPS id 87DC01A00BC for ; Wed, 19 Nov 2025 22:59:21 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=php.net; s=mail; t=1763593166; bh=tcaGzjeDQ+mA+OpgORKLxFHReSrUS6zp1e7PxjOX8fI=; h=References:In-Reply-To:From:Date:Subject:To:Cc:From; b=Eteuq4y3SiJSZLKJnyUQD4Cje2Ao5wrAmtFw4/GwWBu+o4AxOu0TDZ9K/huTM2zY9 NZ7vYdxwKWO+Dzdh5Ve/yFu6OKcl/cYHXOOk+pd4GsACxDwczCmXxIlWq4Mo/IrtnB gnFHCd2nfxdmMBvjLSYhmxTy4pBtsMIu3DHQbU2U4l/PK0Y7h0Ipm5LGXzjU/KyEww zKzb2nZOHRv/ua5Vgy+UOoevsiqPwvfWxkJNOusHTt1hU1NYSgw3kruz98BUKSZ7lT U6zVRCRVRGiPXFMpMCNPfaFL652IjuuShKp9dxJd13TEnskJt4B8EI7uAtqgfJSsif eK5qPKd98EmqQ== Received: from php-smtp4.php.net (localhost [127.0.0.1]) by php-smtp4.php.net (Postfix) with ESMTP id CCDD418038B for ; Wed, 19 Nov 2025 22:59:24 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 4.0.1 (2024-03-25) on php-smtp4.php.net X-Spam-Level: * X-Spam-Status: No, score=1.8 required=5.0 tests=BAYES_50,DMARC_NONE, FREEMAIL_FORGED_FROMDOMAIN,FREEMAIL_FROM,HEADER_FROM_DIFFERENT_DOMAINS, HTML_MESSAGE,RCVD_IN_DNSWL_NONE,RCVD_IN_MSPIKE_H3,RCVD_IN_MSPIKE_WL, SPF_HELO_NONE,SPF_PASS autolearn=no autolearn_force=no version=4.0.1 X-Spam-Virus: No X-Envelope-From: Received: from mail-oo1-f42.google.com (mail-oo1-f42.google.com [209.85.161.42]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by php-smtp4.php.net (Postfix) with ESMTPS for ; Wed, 19 Nov 2025 22:59:14 +0000 (UTC) Received: by mail-oo1-f42.google.com with SMTP id 006d021491bc7-657534478a0so94467eaf.1 for ; Wed, 19 Nov 2025 14:59:09 -0800 (PST) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1763593149; x=1764197949; h=cc:to:subject:message-id:date:from:in-reply-to:references :mime-version:x-gm-gg:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to; bh=Xul07QcltvrUhaExBFivfNjoz1wQXd8lNdKOBCz4e5w=; b=fxVMqptg8cgRLigW6sZT8kFuHpQmQXk3BUGF0PqshjJAIEQJjyIEr1uJBybJJX1IfU AaF/50ieUyb5kVJYjQ4d1HC5WE7PWpveqj1KAqxNk/sWI5MB+87m5F7Ic1iXKCSw98+5 aVwQoMsb/n3y7Pmd7GV5W+22Yodj5NAdAzz8MHtEpZblQyZYbF051IUu3zqHlcc7ymcT AKDMt3p1zJJyFndocKKsgM1486isEWK71xVxx0/wS3JV19kCKIG1GICxfa0fubxqVGpo n899ebw8bCZYKcC7nmuseFdFKj+19GSH9XX7Nkd9w7Cuhix5nmt3qSAVu87F4q00QVes WPGQ== X-Gm-Message-State: AOJu0YzE/m3WyuArQeJJN6EbeAAlYNaJmmypb1EFWRrMaPHw1lM5RY9j YaVYTSJSkSEx0Jf2NnfIHu6eYDLQPsQ33Wt5p5qml5pkHSnvQkSZG599sgjbfoQIxjPhSt1WKHP 9WQnOaN1ha4/X/xh0kveq1E9UDImFxvw= X-Gm-Gg: ASbGnctdOCa6jlJCnWct4fbrsPBQiFExqOyMwQw1y7NCgL/zo9UhbXhJfB+b6RM4q0W HVtvRi0xfMb1xd0SjF/nqyQ/6ocArYigNnpWTFcwUB5+vbEHuZ33U5nUUoAqlk5vSgW2DZq+v5R VCvjdiAd0mwrnejfnXfOY6zNprB9yBY/gXs8Uu5InD1GxouwdLuUVX9b8Rc6tNIpZC93wnNeVax npEHr0JDUUvgQlq1Ihg/OQkKahTMLksFcOq6jCcb6b8ztp9xbA6FoCE+xdQIcrT7uvdRR1YnPfw YOlf X-Google-Smtp-Source: AGHT+IFYAmyAqhPBOqDCiPuaCdOqKujvRA9iy6stx1t+1IyYdh13ekMW0fUeXcdCd5JShm4r+GcQsqYE4aDNbw2kppI= X-Received: by 2002:a05:6808:4489:b0:44f:e931:38ab with SMTP id 5614622812f47-450ff387ac9mr454223b6e.43.1763593148738; Wed, 19 Nov 2025 14:59:08 -0800 (PST) Precedence: list list-help: list-unsubscribe: list-post: List-Id: x-ms-reactions: disallow MIME-Version: 1.0 References: In-Reply-To: Date: Wed, 19 Nov 2025 23:58:57 +0100 X-Gm-Features: AWmQ_bnuH6CHyv3V2UsFT0i6Mv2BG4tKPO6TGHdKcMWkLRKHZbTgis3eK5AMxq4 Message-ID: Subject: Re: [PHP-DEV] [RFC] Stream Error Handling Improvements To: Bob Weinand Cc: PHP internals list Content-Type: multipart/alternative; boundary="00000000000028afce0643fa8756" From: bukka@php.net (Jakub Zelenka) --00000000000028afce0643fa8756 Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable Hi Bob, On Wed, Nov 19, 2025 at 5:37=E2=80=AFPM Bob Weinand w= rote: > Hey Jakub, > > thanks for the detailed reply. > On 19.11.2025 12:52:35, Jakub Zelenka wrote: > > The code one should be probably named StreamErrorCode (to not interact > with what Jordi proposed) and think it will need to be backed as there > should be a straightforward way how to compare it with exception code. I > might need to extend the gen stub as I'm not sure it allows macro import > for enum value like for constants or I will need to come up with some > mapping. I just don't wan to duplicate the numbers. I will figure somethi= ng > out. > > How important is it to have exception code? > I think the exception code could be quite useful for checking for specific errors. It's much better than trying to match it by error message which I saw in past used in normal error handlers. So I can see definitely use case for that. It might be also possible to group those erors if enums are used so users could just match specific group of errors. > I found that for our purposes we generally skip the exception code (i.e. > keep it at zero). > As a simple example, ErrorException does take two integers - one for > severity, and one for code, instead of using the severity as code. > Severity is almost always warning which I don't think is a good representation for code. I saw error codes used in various application and I think they are useful. I don't think it hurts to have them. The implementation of that enum should not be a big problem. I just need to figure out how to make it nice. > I would propose something similar here: call it StreamError and provide a > public $error property for the actual enum, rather than squeezing it into > $code. > I'm not sure I understand this. The code is a generic identifier for the error class that can be matched. StreamError would be the actual representation containing the message and other info - this cannot be matched in a generic way. > 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. >> > > Yeah the logged errors is an existing mechanism for grouping errors to a > single one (concatenating message). The display is a bit messy because it > separated them by new line or
(if html errors enabled) but I just > kept it to limit BC impact. > > I don't think changing the display / grouping here would be problematic > with respect to BC. But I get that this initial draft tries to limit what > it changes. > Exactly I would like to limit the external changes. > > 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. >> > > This is somehow related to the exception error grouping as it requires > some sort of linking between the errors. > > I guess to make this work we would need some top level wrapping similar t= o > what logged errors do but instead of concatenating, it would group the > errors in a better way which could then be used in exception or handler. > This would, however, increase the complexity significantly as it is > possible to nest the stream calls (e.g. stream calls in notification > callback or in the new error handler) so there would probably need to be > some stack. Also I'm not sure about the API. Should we pass always array = of > errors to the callback (which would mostly have just one element anyway o= r > somehow chain the StreamError object (something like previousError()) and > what message should be used in exception (e.g. last error and then helper > function to get all errors)? > > I'm willing to look into this but would like to know if this is something > important for user space and what is the usual flow how could this be use= d > in frameworks and so on. In other words I would like to get more input to > make sure that this is really needed as it requires quite a bit of effort > to do. > > Doing this work will definitely tidy up the whole error story. It's not > fundamentally crucial, but it definitely will make for a nicer API. > > And yes, chaining StreamError sounds and nesting exceptions to $previous > sounds like the most straightforward way. > > Also a possibility for exceptions: this might be a bit of work, but you > could insert a fake frame for exceptions to indicate when an error appear= s > as a sub-operation. Like: > > StreamException: Permission denied > #0 [internal function]: fopen("target.php", "w+") > #1 [internal function]: copy("source.php", "target.php") > #2 ... > > I will check this out. > I also like the suggestion by Jordi to pass the StreamError right away > into the user handler. > > > Yeah agreed. > 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. >> >> > Currently yes. I need to actually double check the consequences of that a= s > I didn't really think about internal resources. > > If we decide to go with completely wrapping and calling the user error > handler only once per top-level operation, this point would be moot anywa= y. > > > > 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. >> >> > I guess this makes more sense but it kind of also implies the top level > wrapping as it should contain all errors in the call. Or alternatively it > could be just a ring with limited number of errors be behave in the same > way like openssl_error_string. I guess it depends if we go with that top > level wrapping. > > I feel like the top-level wrapping would simplify things a lot (from poin= t > of addressing shortcomings), and if this just works like > json_get_last_error(), where you only ever see the last error, there's no > risks of being unbounded nor having arbitrary size limits. > Yeah I think it would be more logical and improve things so I will check it out and see when I have my next stream errors slot (should be later next month). Cheers, Jakub --00000000000028afce0643fa8756 Content-Type: text/html; charset="UTF-8" Content-Transfer-Encoding: quoted-printable
Hi Bob,

On Wed, Nov 19, 2025 at = 5:37=E2=80=AFPM Bob Weinand <bobw= ei9@hotmail.com> wrote:
=20

Hey Jakub,

thanks for the detailed reply.

On 19.11.2025 12:52:35, Jakub Zelenka wrote:
=20
The code one should be probably named StreamErrorCode (to not interact with what Jordi proposed) and think it will need to be backed as there should be a straightforward way how to compare it with exception code. I might need to extend the gen stub as I'm not sure it allows macro import for enum value like for constants or I will need to come up with some mapping. I just don't wan to duplicate the numbers. I will figure something out.

How important is it to have exception code?

I think the exception code could be quite useful for checking for specif= ic errors. It's much better than trying to match it by error message wh= ich I saw in past used in normal error handlers. So I can see definitely us= e case for that. It might be also possible to group those erors if enums ar= e used so users could just match specific group of errors.

I found that for our purposes we generally skip the exception code (i.e. keep it at zero).
As a simple example, ErrorException does take two integers - one for severity, and one for code, instead of using the severity as code.

Severity is almost always warnin= g which I don't think is a good representation for code. I saw error co= des used in various application and I think they are useful. I don't th= ink it hurts to have them. The implementation of that enum should not be a = big problem. I just need to figure out how to make it nice.

I would propose something similar here: call it StreamError and provide a public $error property for the actual enum, rather than squeezing it into $code.

I'm not sure = I understand this. The code is a generic identifier for the error class tha= t can be matched. StreamError would be the actual representation containing= the message and other info - this cannot be matched in a generic way.
Is this what the logged errors section is about? Is that section talking
about "php_stream_display_wrapper_errors", which ca= n concat some errors?
I see that this logic is mostly retained.

Yeah the logged errors is an existing mechanism for grouping errors to a single one (concatenating message). The display is a bit messy because it separated them by new line or <br/> (if html errors enabled) but I just kept it to limit BC impact.

I don't think changing the display / grouping here would be problematic with respect to BC. But I get that this initial draft tries to limit what it changes.

Exactly I = would like to limit the external changes.


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.

This is somehow related to the exception error grouping as it requires some sort of linking between the errors.

I guess to make this work we would need some top level wrapping similar to what logged errors do but instead of concatenating, it would group the errors in a better way which could then be used in exception or handler. This would, however, increase the complexity significantly as it is possible to nest the stream calls (e.g. stream calls in notification callback or in the new error handler) so there would probably need to be some stack. Also I'm not sure about the API. Should we pass always array of errors to the callback (which would mostly have just one element anyway or somehow chain the StreamError object (something like previousError()) and what message should be used in exception (e.g. last error and then helper function to get all errors)?=C2=A0

I'm willing to look into this but would like to know i= f this is something important for user space and what is the usual flow how could this be used in frameworks and so on. In other words I would like to get more input to make sure that this is really needed as it requires quite a bit of effort to do.

Doing this work will definitely tidy up the whole error story. It's not fundamentally crucial, but it definitely will make for a nicer API.

And yes, chaining StreamError sounds and nesting exceptions to $previous sounds like the most straightforward way.

Also a possibility for exceptions: this might be a bit of work, but you could insert a fake frame for exceptions to indicate when an error appears as a sub-operation. Like:

StreamException: Permission denied
#0 [internal function]: fopen("target.php", "w+")=
#1 [internal function]: copy("source.php", "target.php= ")
#2 ...

I will check this out.

I also like the suggestion by= Jordi to pass the StreamError right away into the user handler.


Yeah agreed.
=C2=A0
<= blockquote class=3D"gmail_quote" style=3D"margin:0px 0px 0px 0.8ex;border-l= eft:1px solid rgb(204,204,204);padding-left:1ex">

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.


Currently yes. I need to actually double check the consequences of that as I didn't really think about internal resources.

If we decide to go with completely wrapping and calling the user error handler only once per top-level operation,=C2=A0this point woul= d be moot anyway.

=C2=A0=C2=A0

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 t= he 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&= #39;d love having a
stream_get_last_error() returning just the code.


I guess this makes more sense but it kind of also implies the top level wrapping as it should contain all errors in the call. Or alternatively it could be just a ring with limited number of errors be behave in the same way like=C2=A0openssl_error_string. I guess it depends if we = go with that top level wrapping.

I feel like the top-level wrapping would simplify things a lot (from point of addressing shortcomings), and if this just works like json_get_last_error(), where you only ever see the last error, there's no risks of being unbounded nor having arbitrary size limits.

Yeah I think it would be more= logical and improve things so I will check it out and see when I have my n= ext stream errors slot (should be later next month).

Cheers,

Jakub=C2=A0
--00000000000028afce0643fa8756--