Hi Tim, Nicolas,

Thanks both for the feedback. I went through the API and the
implementation again and updated the RFC and PR to reflect it.

For Tim's points:

- TerminalSize now has a normal public constructor, with positive
dimensions enforced.
- The RFC now shows the actual stub, including strict-properties /
non-serializable declarations.
- Terminal::create() has been renamed to Terminal::fromStdio().
- enableRawMode() now returns ModeToken and uses TerminalException for
operational/native failures.
- readKey() now has a defined contract: false means timeout/no
complete key before the deadline; non-TTY input, EOF and
native/polling failures are exceptions. The timeout and
sequenceTimeout behavior is now described explicitly in the RFC.
- ModeToken lifetime and restoreMode() behavior are now described in detail.

For the ownership question, I kept ModeToken opaque rather than adding
a public Terminal back-reference.

Nicolas, I also changed the implementation to the shared-per-terminal
model you suggested. The first active token captures the original mode
and owns the restoration descriptor/handle, subsequent tokens share
that state, and the original mode is restored when the last active
token is released. This makes release order irrelevant and also means
closing the original PHP stream does not prevent restoration.

I also reworked the terminal identity handling from the earlier
st_rdev-only approach so different descriptors for the same logical
terminal can coordinate without conflating unrelated PTYs.

The RFC has been updated here:
https://wiki.php.net/rfc/io_terminal

Implementation:
https://github.com/php/php-src/pull/23941

These are substantive API/semantic changes made during discussion, so
this reply is also the announcement of those RFC changes.

Thanks again for taking the time to review this. The
lifecycle/ownership feedback in particular made the model much
cleaner.

Best regards,
Pratik

On Mon, 28 Sep 2026 15:00:09 +0200, Nicolas Grekas
<[email protected]> wrote:
> Hi Pratik,
>
> thanks for the RFC, PHP definitely needs native terminal support, calling 
> stty is a workaround we've been carrying since way too long.
>
> Le lun. 28 sept. 2026 à 11:49, Tim Düsterhus <[email protected]> a écrit :
>
> Hi
>
> On 2026-09-27 19:19, Pratik Bhujel wrote:
>
> > I would like to formally propose the Io\Terminal API for PHP 8.7:
>
> >
>
> > https://wiki.php.net/rfc/io_terminal
>
> Thank you for the RFC. Some questions to start of the discussion:
>
> 1. Should TerminalSize have a regular constructor? It seems to be safe
>
> to allow constructing it from userland, e.g. for testing purposes.
>
> 2. It would help readability if the stub would indicate the
>
> non-serializability (and strict properties) instead of mentioning it in
>
> the prose. Basically you can just take the stub file from your PR and
>
> include it in the RFC.
>
> 3. Terminal::create() should probably be ::fromStdio() or similar.
>
> 4. I'm not sure about false vs Exception for the various methods.
>
> `enableRawMode()` should probably be Exception, for `readKey()` the
>
> `false` return is not explained. Also the behavior of what happens when
>
> a timeout strikes is not explained.
>
> 5. Should ModeToken have a property that points back to the
>
> corresponding Terminal? Overall the interaction between the destructors
>
> and ModeToken and restoreMode should be explained more. As an example,
>
> what happens if the Terminal object dies before the ModeToken object?
>
> What if I create two Terminal objects for the same terminal?
>
> Related to ModeToken, what about a refcount instead? One record per terminal, 
> shared by all its tokens: the first enableRawMode() saves the original 
> termios and dup()s the fd into the record, the next ones only increment a 
> counter, and releasing any token decrements it. At zero, the original state 
> is restored through the record's own fd. That's order-independent by 
> construction and immune to fclose(). The RFC could then spec it in one 
> sentence: raw mode stays on as long as one token for that terminal is alive, 
> and the state from before the first token is restored when the last one goes 
> away. Today it says a token "restores its saved terminal mode when it is 
> destroyed", which isn't what happens out of order. If more modes are added 
> later, the record can keep the set of active requests and recompute the 
> target state from the original on each change.
>
> Cheers,
>
> Nicolas

Reply via email to