Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:130414 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 D16F91A00BC for ; Sun, 22 Mar 2026 19:27:03 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=php.net; s=mail; t=1774207628; bh=smF9f0NoOFjOLiVSoEW8errotqV6uniQT+qSwYlBzZ0=; h=References:In-Reply-To:From:Date:Subject:To:Cc:From; b=ZzbwQgD0Vqe+6X8DQQNFzLItV4A9pWlrK7ZQ2lfuk2eK9vgLfM/P0qWv7KZ2IVpmc Jw9KUY7JwrvDhvXtikxOexts2LQiYwHVe5OTikuK8+jDSQCCzCSPwDTJEWLW9R7Ckc CsypFuSEnqqT8bT6FfnXriOOM1WOJK503bCUvwwOjZ+nIZu66w7cNo4sd9Uoowifqa duSH6mAzyOJj1q19XlaKiiv6z0Lxa+xhwX/k4+Cl3L73u2MhUviLCmfzXPqSBeWv6e xTKVxKaMIrMvjvKM/XqYedwKQiD8J4tOWxQ7C5YKeXFllR5eIfDe3H53ghBO8GiAyH 7DTAOqk/OSfQA== Received: from php-smtp4.php.net (localhost [127.0.0.1]) by php-smtp4.php.net (Postfix) with ESMTP id 9A455180069 for ; Sun, 22 Mar 2026 19:27:06 +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.7 required=5.0 tests=ARC_SIGNED,ARC_VALID,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-f44.google.com (mail-oo1-f44.google.com [209.85.161.44]) (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 ; Sun, 22 Mar 2026 19:27:01 +0000 (UTC) Received: by mail-oo1-f44.google.com with SMTP id 006d021491bc7-67ba5921b84so1250940eaf.3 for ; Sun, 22 Mar 2026 12:26:56 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1774207615; cv=none; d=google.com; s=arc-20240605; b=hVEdvkyDehUPXd9l8BbfcDHpAiTwwmXJ7jJNqlBWyqoNKtzHEdysNeUOWYkZDRaeRI VdXmo1wD0gDptnlxgnpRMSq5WbnfB4ncnsgbXoYH+SoQ7wDSPhv/6jnOvafrtHAlRHe9 TDl9XTvUocKZrDHef0tawQ3n2YO6EZhYoqWIac6QoHiXjHSMBsz3I0Xo4DcWem80SHWl iH07JupQl46m7qe8j6nFgxpvau9EA/qaxucA1Z9r1F/ZGu4oIBpP/QxLrdYXCnI7KLZN Azp3Ug0AtT9CW350JiM3yNV5heGYaZeEFLCSvGYCGAa23irGS6HwtWpwQIq35PK31/MH 9uhg== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20240605; h=cc:to:subject:message-id:date:from:in-reply-to:references :mime-version; bh=ybS8DdIPW02BM535bbZ1xO9kus0YE1c3orgn52cwRNQ=; fh=rdr3p/kyVdJVePevk6jIeW8X/IMH063M4Pe3odJwSo8=; b=Ja1HAt6sY9D5tD/gaIa4QGt2F2AuYlF6Rn9DHaEiFoDZX2tpHcVYYEAqncauoZ808T avjk9rbeDYkZKyNLplnstVdeZ3cSfCAodYpNYfOU3uBKp02Zz5aG87jx0L9AysxbUHa6 O4mM23RtOQCZkcikjcCqe7TSafhUEop+KiELkYxp2qCO5NPJBOCYps/s6cyZc6RGdBVl HCWkgMKZMsxwPWUm/FcWTl7rUw8ttouCFambYqlGi+sDgAxMUGM8xd75Gq4sB8uF1Sk8 KGiGFlpTNTska3FRdWbhplX+JGPCVDNT6wTB4oDXC+b0aVYEW7YBovhLmKUs1r0EMPhw oO/g==; darn=lists.php.net ARC-Authentication-Results: i=1; mx.google.com; arc=none X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1774207615; x=1774812415; 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=ybS8DdIPW02BM535bbZ1xO9kus0YE1c3orgn52cwRNQ=; b=kFm3sbGsioYwwmVPYFBTvwQYINnBfnQ9Gd6sX53PYP8HxBZGjvy9VmB14YOmaS1hE6 IWi8tOsLLGBrOdZPXLPLKaTUhT6hzdmCoQQh3ES4cAb9QldRMxAygkDEERKOdAhmKWRn nGY7+/PRHbMP81affU/Y+YmHAlr7spLHTXvghovBbFBcG9eAq1e7EPvwdHemCBCFHRU4 AXikpDJvSjDTYTi5Bg6WZcgi0/TuIh/FkbstpnLBeg/bRUUWxHPLStE+MDIfrVMM1jiG uy3uw2OoVJpTI5sbJyH+9EW23Fdy+PU7iDDscO75YHjbyNZqqM+OdH4/lbOE3Xe293ip CQKw== X-Gm-Message-State: AOJu0Ywi+E2MUvWiB7gz0uCuARV5Ac6Zcd3fQFTCUTjGPiold5baLzsf KqawN7XTn2fvkrfKuJxjeMIxFJe3boawevfIT5ZulRpQv/+jQg5zh6DlxU0vdvuEKZAYBYmMky5 kM/+xBRqAe2QKpDAj/A2CLhzEGewm+RA= X-Gm-Gg: ATEYQzzNzhF9yaoBfzyNKPZUr2FiMfsrVmotp095QraiCcP069uRgWgLVPfsIldRl// A9w4ezh5PSc8VUb9jgt93qZMfbvR2MoEHDoEOr50S8mK0kdlbYE6K6ISptBnzBq4QfyBoYNksm5 iWiQNIe4y3BFYsY9vC7MwueGfB5ls0Z4jOhUyV+A5v+Zt80k6L5GveyzbNGPGiP7GbmKv6ID5Ym 9Q1y1g2II523P4XMRxlKZhlR+kwjlg6tjDa/fAAOPRiRKalImNG5KGRLSDo+/OFhErzUApIYyKN jH3AncY= X-Received: by 2002:a05:6820:c83:b0:67d:b2f2:6e81 with SMTP id 006d021491bc7-67db2f27279mr3610154eaf.0.1774207615283; Sun, 22 Mar 2026 12:26:55 -0700 (PDT) Precedence: list list-help: list-unsubscribe: list-post: List-Id: x-ms-reactions: disallow MIME-Version: 1.0 References: In-Reply-To: Date: Sun, 22 Mar 2026 20:26:43 +0100 X-Gm-Features: AaiRm51mX0iRwNzzdkhuyYMux1I3M7xw2HpUP5bHat49NAB7Z6tKJO-sYgoNBr0 Message-ID: Subject: Re: [PHP-DEV] [RFC] Stream Error Handling Improvements To: Pierre Joye Cc: PHP internals list Content-Type: multipart/alternative; boundary="000000000000aab844064da1e693" From: bukka@php.net (Jakub Zelenka) --000000000000aab844064da1e693 Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable Hi, Sorry for slightly late reply. I wanted to think about it and was also busy with other stuff. On Sun, Mar 1, 2026 at 4:09=E2=80=AFPM Pierre Joye w= rote: > Hi Jakub, > > On Tue, Dec 23, 2025 at 6:10=E2=80=AFAM Jakub Zelenka wro= te: > > > > 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 > > Thank you for this RFC! . It is clearly needed and the error chaining > design is great. There are a few points I'd like to raise, the last > two being slightly off topic specifically to that rfc but however very > related to. So a slightly long reply. > > 1. New exceptions > > The current design has a single flat StreamException. This means the > exception path requires inspecting the error object to branch on > category: > > catch (StreamException $e) { > if ($e->getError()->isNetworkError()) { > // retry > } elseif ($e->getError()->code =3D=3D=3D StreamError::CODE_NOT_FOUND)= { > // fallback > } > } > > The current design introduces StreamException, which is good. But as a > single flat class it doesn't actually allow to use exceptions the way > exceptions are meant to be used. One still has to inspect the error > object inside the catch block to know what happened. It introduces the > syntax of exception handling without the benefit of it. > > Since the error categories are already well-defined and stable in this > RFC, StreamException could be a base class with typed subclasses > mirroring those categories: > > StreamException > =E2=94=9C StreamIoException > =E2=94=9C StreamFileSystemException > =E2=94=9C StreamNetworkException > =E2=94=9C StreamWrapperException > =E2=94=94 ... > > > This allows the more natural and idiomatic: > > catch (StreamNetworkException $e) { > // retry =E2=80=94 type alone tells you what happened > } catch (StreamNotFoundException $e) { > // fallback > } catch (StreamException $e) { > // generic > } > > The StreamError value object can stay as in the RFC (and keep "BC" for > the error const's bag), it's appropriate for the inspection/silent > mode use case. I see the typed exception hierarchy and the flat value > object serve different purposes and complement each other: > $e->getError() still gives you full detail when you need it. These are > separable concerns currently merged into one class. > I thought about this a lot and I'm not sure this would be that useful as I don't see a big enough use case for cetegorization on exception level. It was exposed more as a nice to have but I doubt that users will care that much if the error is an Io or FileSystem category. It seems also slightly messe to have the exception class dependent on the error category that it holds as a property. I would possibly reconsider it if I saw a big use case for categorization but I see it more as a nice to have utility which doesn't need to propagate in this way. > Since StreamException doesn't exist in any current PHP version, there > is no existing userland code catching it. This is a clean state. > Adding a typed hierarchy now costs nothing in BC terms, and > retrofitting it later would be a BC break. > I don't think it would be an unacceptable BC break to split it later if there was a really need for that. The parent would still stay StreamException and all code that catches it would still work. I don't think any reasonable code would relay on the exception to be exactly StreamException and not it's subclass so I wouldn't see that as an issue form the BC point of view. > It is more than just syntactic sugar, it allows very clean logic. > static analysis tools can reason about which exceptions a function > throws, IDEs can suggest catch blocks, and retry wrappers can target > network errors specifically without accidentally swallowing filesystem > errors. The cost of adding the hierarchy now is low; retrofitting it > later is a BC break. > As I said above I really don't see much use case in just targeting network errors. That's really niche use case IMHO and for that it can just get it from the StreamError. My main issue with this is that dependency between StreamException subclass and StreamError. > > 2. Custom stream wrapper and errors > > How does custom wrappers, be in ext/, or out of core wrappers, exts or > user land, can emit errors? Or is it out of the scope of this RFC? > > The wrapper needs to be adapted and call the new API functions. It might also require additional global types if needed (most of the wrappers should be fine with existing ones but there might be use case to add more). > For wrappers authors, f.e. in zip, php_error_docref(NULL, E_WARNING, > "Zip stream error: %s", zip_error_strerror(err));, or > php_stream_wrapper_log_error() it is the classic php error. Both were > acceptable and the only way available, without relying on custom > methods, but if the stream's error handling is being improved, or made > future proof (when possible), together with the typed exception, we > could get something good. The typed exceptions give callers a clean > API. Adding an emit function gives wrapper authors a clean API. They > close the loop. Either one without the other leaves the whole thing > incomplete. > If typed exceptions exist and stream_emit_error in userland and > php_stream_emit_error internally exist, wrappers can emit errors that > integrate cleanly with the catch hierarchy, and have better error > reporting. > > The internal API is working just with types that fall into some categories. I don't see any relation to typed exception here. The API is just about emitting typed errors through. > Some of the future scopes seem foundational to stream error handling > improvements, and risky to delay rather than allowing them now. > > Covering all existing wrappers from the start is just not realistic. I actually started with generic type registration but it got quite complex and it would require some unstable type registration which couldn't be exposed as constants. I also realised that the actually types are repeating between wrappers so it was just better to switch to global types. That extension type ranges in future scope was actually a left over when I was still considering this dynamic registration. I just removed it. > the slightly off topic ones: > > Is there a published overall design document for the stream evolution > project? The foundation blog post outlines four pillars at a (very) > high level, but the relationship between the RFCs, their > sequencing/order rationale to be added already, and the intended end > state of the streams API are not spelled out anywhere I can find. > Having that available would make it much easier to give a more > informed feedback on each RFC as it arrives, and to flag conflicts or > gaps early. > > I got some future improvements in my head but the short term plan is just what was published and what we have funding for. The rest depends on further funding and available resources to do the actual work so there is not much point in too detailed plan. > Streams were introduced over two decades ago (afair it was in the > early 2000s, by Wez. we aren't younger :), and much has changed since. > Async runtimes (swoole, reactphp, FrankenPhp to some extent, etc), > fibers, modern TLS, io_uring, and very different application patterns. > The error RFC itself acknowledges the BC constraints that shape its > design. My concern is that modernizing streams incrementally in 8.x, > under those constraints, risks producing something that is improved > but still not fit for where PHP needs to go, or ideally should go, > particularly around async I/O, where error handling, stream selection, > and the overall API surface will need to work together in a coherent > manner. > > Is there a published async I/O architecture that these RFCs are > building toward? The other stream related RFCs or the phpf's blog > post mention future work about async IO, and related stream topics, > which suggests the async design exists internally but hasn't been > shared publicly. If that's the case, it would be fantastic to share it > (even as a draft design doc), so it can be assessed whether the > current incremental steps are pointing in the right direction, or > whether some decisions being made now will need to be undone later (or > will be impossible to undo without breaking BC). Especially if authors > of Swoole (made io uring into their php 8.5 version it seems :), > reactphp, etc would surely provide extremely good feedback too. > > The polling RFC is the first step that also introduces new IO handle abstraction. I'm building on that internally in new copy API that also uses those new handles. What I plan to look after polling is the notification API that would all IO notifications before the actual IO with future possibility of taking over the IO operation. Then I'm also experiment with new IO ring API (basically io_uring with fallbacks for other platforms) that would allow async IO handling. I don't have more details at this stage but there should be RFC's coming in the second half of this year where more details will be shared. Kind regards, Jakub --000000000000aab844064da1e693 Content-Type: text/html; charset="UTF-8" Content-Transfer-Encoding: quoted-printable
Hi,

Sorry for slightly= late reply. I wanted to think about it and was also busy with other stuff.=

On Sun, Mar 1, 2026 at 4:09=E2=80=AFPM Pierre= Joye <pierre.php@gmail.com&= gt; wrote:
Hi Ja= kub,

On Tue, Dec 23, 2025 at 6:10=E2=80=AFAM Jakub Zelenka <bukka@php.net> 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 Te= ch Fund) :
>
> https://wiki.php.net/rfc/stream_errors

Thank you for this RFC! . It=C2=A0 is clearly needed and the error chaining=
design is great. There are a few points I'd like to raise, the last
two being slightly off topic specifically to that rfc but however very
related to. So a slightly long reply.

1. New exceptions

The current design has a single flat StreamException. This means the
exception path requires inspecting the error object to branch on
category:

catch (StreamException $e) {
=C2=A0 =C2=A0 if ($e->getError()->isNetworkError()) {
=C2=A0 =C2=A0 =C2=A0 =C2=A0 // retry
=C2=A0 =C2=A0 } elseif ($e->getError()->code =3D=3D=3D StreamError::C= ODE_NOT_FOUND) {
=C2=A0 =C2=A0 =C2=A0 =C2=A0 // fallback
=C2=A0 =C2=A0 }
}

The current design introduces StreamException, which is good. But as a
single flat class it doesn't actually allow to use exceptions the way exceptions are meant to be used. One still has to inspect the error
object inside the catch block to know what happened. It introduces the
syntax of exception handling without the benefit of it.

Since the error categories are already well-defined and stable in this
RFC, StreamException could be a base class with typed subclasses
mirroring those categories:

StreamException
=E2=94=9C StreamIoException
=E2=94=9C StreamFileSystemException
=E2=94=9C StreamNetworkException
=E2=94=9C StreamWrapperException
=E2=94=94 ...


This allows the more natural and idiomatic:

catch (StreamNetworkException $e) {
=C2=A0 =C2=A0 // retry =E2=80=94 type alone tells you what happened
} catch (StreamNotFoundException $e) {
=C2=A0 =C2=A0 // fallback
} catch (StreamException $e) {
=C2=A0 =C2=A0 // generic
}

The StreamError value object can stay as in the RFC (and keep "BC"= ; for
the error const's bag), it's appropriate for the inspection/silent<= br> mode use case. I see the typed exception hierarchy and the flat value
object serve different purposes and complement each other:
$e->getError() still gives you full detail when you need it. These are separable concerns currently merged into one class.
I thought about this a lot and I'm not sure this would be = that useful as I don't see a big enough use case for cetegorization on = exception level. It was exposed more as a nice to have but I doubt that use= rs will care that much if the error is an Io or FileSystem category. It see= ms also slightly messe to have the exception class dependent on the error c= ategory that it holds as a property. I would possibly reconsider it if I sa= w a big use case for categorization but I see it more as a nice to have uti= lity which doesn't need to propagate in this way.
=C2=A0
Since StreamException doesn't exist in any current PHP version, there is no existing userland code catching it. This is a clean state.
Adding a typed hierarchy now costs nothing in BC terms, and
retrofitting it later would be a BC break.

<= div>I don't think it would be an unacceptable BC break to split it late= r if there was a really need for that. The parent would still stay=C2=A0Str= eamException and all code that catches it would still work. I don't thi= nk any reasonable code would relay on the exception to be exactly StreamExc= eption and not it's subclass so I wouldn't see that as an issue for= m the BC point of view.=C2=A0
=C2=A0
It is more than just syntactic sugar, it allows very clean logic.
static analysis tools can reason about which exceptions a function
throws, IDEs can suggest catch blocks, and retry wrappers can target
network errors specifically without accidentally swallowing filesystem
errors. The cost of adding the hierarchy now is low; retrofitting it
later is a BC break.

As I said above I = really don't see much use case in just targeting network errors. That&#= 39;s really niche use case IMHO and for that it can just get it from the St= reamError. My main issue with this is that dependency between StreamExcepti= on subclass and StreamError.
=C2=A0

2. Custom stream wrapper and errors

How does custom wrappers, be in ext/, or out of core wrappers, exts or
user land, can emit errors? Or is it out of the scope of this RFC?


The wrapper needs to be adapted and ca= ll the new API functions. It might also require additional global types if = needed (most of the wrappers should be fine with existing ones but there mi= ght be use case to add more).
=C2=A0
For wrappers authors, f.e. in zip, php_error_docref(NULL, E_WARNING,
"Zip stream error: %s", zip_error_strerror(err));, or
php_stream_wrapper_log_error() it is the classic php error. Both were
acceptable and the only way available, without relying on custom
methods, but if the stream's error handling is being improved, or made<= br> future proof (when possible), together with the typed exception, we
could get something good. The typed exceptions give callers a clean
API. Adding an emit function gives wrapper authors a clean API. They
close the loop. Either one without the other leaves the whole thing
incomplete.

If typed exceptions exist and stream_emit_error in userland and
php_stream_emit_error internally exist, wrappers can emit errors that
integrate cleanly with the catch hierarchy, and have better error
reporting.


The internal API is working just with = types that fall into some categories. I don't see any relation to typed= exception here. The API is just about emitting typed errors through.
=
=C2=A0
Some of the future scopes seem foundational to stream error handling
improvements, and risky to delay rather than allowing them now.


Covering all existing wrappers from th= e start is just not realistic. I actually started with generic type registr= ation but it got quite complex and it would require some unstable type regi= stration which couldn't be exposed as constants. I also realised that t= he actually types are repeating between wrappers so it was just better to s= witch to global types.=C2=A0

That extension type r= anges in future scope was actually a left over when I was still considering= this dynamic registration. I just removed it.
=C2=A0
the slightly off topic ones:

Is there a published overall design document for the stream evolution
project? The foundation blog post outlines four pillars at a (very)
high level, but the relationship between the RFCs, their
sequencing/order rationale to be added already, and the intended end
state of the streams API are not spelled out anywhere I can find.
Having that available would make it much easier to give a more
informed feedback on each RFC as it arrives, and to flag conflicts or
gaps early.


I got some future improvements in my h= ead but the short term plan is just what was published and what we have fun= ding for. The rest depends on further funding and available resources to do= the actual work so there is not much point in too detailed plan.
=C2=A0
Streams were introduced over two decades ago (afair it was in the
early 2000s, by Wez. we aren't younger :), and much has changed since.<= br> Async runtimes (swoole, reactphp, FrankenPhp to some extent, etc),
fibers, modern TLS, io_uring, and very different application patterns.
The error RFC itself acknowledges the BC constraints that shape its
design. My concern is that modernizing streams incrementally in 8.x,
under those constraints, risks producing something that is improved
but still not fit for where PHP needs to go, or ideally should go,
particularly around async I/O, where error handling, stream selection,
and the overall API surface will need to work together in a coherent
manner.

Is there a published async I/O architecture that these RFCs are
building toward?=C2=A0 The other stream related RFCs or the phpf's blog=
post mention future work about async IO, and related stream topics,
which suggests the async design exists internally but hasn't been
shared publicly. If that's the case, it would be fantastic to share it<= br> (even as a draft design doc),=C2=A0 so it can be assessed whether the
current incremental steps are pointing in the right direction, or
whether some decisions being made now will need to be undone later (or
will be impossible to undo without breaking BC). Especially if authors
of Swoole (made io uring into their php 8.5 version it seems :),
reactphp, etc would surely provide extremely good feedback too.


The polling RFC is the first step that also in= troduces new IO handle abstraction. I'm building on that internally in = new copy API that also uses those new handles. What I plan to look after po= lling is the notification API that would all IO notifications before the ac= tual IO with future possibility of taking over the IO operation. Then I'= ;m also experiment with new IO ring API (basically io_uring with fallbacks = for other platforms) that would allow async IO handling. I don't have m= ore details at this stage but there should be RFC's coming in the secon= d half of this year where more details will be shared.

=
Kind regards,

Jakub
--000000000000aab844064da1e693--