Io\Terminal proposal
Under discussion; not accepted. The RFC page (version 0.4; the page keeps the 2026-09-27 date) and the official RFC index both list it as under discussion. Its proposed version is PHP 8.7. The voting thread reads "tbd", and no vote has been opened.
The proposal would add native terminal primitives to ext/standard, for POSIX terminals and
Windows consoles. Today libraries call tools such as stty or use the Windows Console API
directly.
Proposed API (version 0.4)
Version 0.4 was published on 2026-10-05 (20:37 UTC wiki edit). It replaces version 0.3's userland-implementable interfaces with concrete classes.
Io\Terminal\Terminalis a single final native class with a private constructor. Instances come only fromTerminal::fromStdio()orTerminal::fromStreams($input, $output = null).fromStdio()resolves the standard descriptors when an operation runs, whilefromStreams()keeps the supplied streams.TerminalandModeTokencannot be cloned or serialized.ModeTokenis a final opaque class that userland cannot construct.enableRawMode()returns one lease token, andrestoreMode(ModeToken $mode)releases it. Leases are shared per logical terminal within a request, and the last release restores the saved mode. Destroying a token and request shutdown restore the mode only on a best-effort basis. A token inherited afterpcntl_fork()cannot be explicitly restored in the child.getSize(): ?TerminalSizereturnsnullwhen no usable size is available. It does not fall back toCOLUMNS/LINES.TerminalSizeis a final readonly value object with a public constructor that throwsValueErrorfor non-positive dimensions.readKey()returns aKeyenum case (arrows, function keys,Resizeand so on), a string ornullon timeout.readLine()reads one line and returnsnullon immediate EOF; it has no timeout and throws while a raw-mode lease is active on the same native terminal.readSecret()reads input without echoing it; Escape, Ctrl+C and Ctrl+D cancel it with an exception. Timeouts areTime\Durationvalues, from the Duration class RFC, which the official index lists as implemented.- Operational failures, and EOF during
readKey()/readSecret(), throwIo\Terminal\TerminalException, declared as extending\Io\IoException. Invalid arguments such as negative durations or consumed tokens throwValueError.
The declared API block contains five Io\Terminal\ symbols: TerminalException, Key,
TerminalSize, ModeToken and Terminal. The RFC lists prompts, history, completion, line
editing and the existing readline extension as outside its scope. Code that already declares
those names would conflict with them.
Evidence and history
The author's internals announcement names the
ext-terminal extension (Packagist prateekbhujel/php-terminal) as the reference
implementation and draft php-src PR #23941 as the
core implementation. Version 0.3's discussion status recorded that, after review on the list, the
initial draft's Terminal::create() was replaced by SystemTerminal::fromStdio(), the
interfaces and native classes were renamed, readLine() was added, and failures and timeouts
now use TerminalException and null. Version 0.4 then removed the interfaces and renamed SystemTerminal to
Terminal (see below). Whether PR #23941 already matches version 0.4 was not checked.
Testing approach: interfaces removed
Version 0.3 had Terminal and ModeToken interfaces, added after a request for a way to mock
the terminal. Tim Düsterhus and Bob Weinand argued that
the TTY stream, not the terminal object, is the IO boundary, and that a userland ModeToken
could not be restored by the native terminal. Version 0.4 follows that view. Ordinary PHP
streams passed to fromStreams() now provide scripted input for tests: readKey() decodes the
bytes, readLine() reads through the stream layer, readSecret() does not echo the input to the
output stream, and enableRawMode() returns a no-op lease instead of failing. The RFC states that
such streams do not simulate terminal dimensions, resize events or native mode failures, and that
applications needing replaceable implementations can define their own abstraction. Whether
version 0.4 settles the list discussion is not yet known. The inspector truncated the RFC page at
18,000 characters, so its later sections (including any changelog) were not read.
Stream abstraction question
On 2026-10-06 Jakub Zelenka asked whether the
API should use some stream abstraction. The message notes that streams are not used directly in
Io so far: the underlying file descriptors are abstracted through Poll\Handle
implementations, which IO hooks also use for direct reads and writes. The message calls this
something to consider, not a blocker. The RFC author
replied on 2026-10-07, leaning towards keeping
fromStdio() and fromStreams() in this RFC. The reply says fromStreams() should keep the
supplied stream's buffering and input behaviour, including php://memory, and that the API also
needs the native terminal for mode changes and Windows console input. A factory for a shared
abstraction could be added later, but ownership and buffering rules would still need work. The
reply asks which abstraction was meant; no answer had been collected when this page was updated.
The RFC page (version 0.4) has not changed.
Vote widget added
On 2026-10-09 at 12:40 UTC the author said they had left the voting widget out of the RFC and had now added it, with the API unchanged. The wiki change feed records the edit ("Add voting choices.") at 12:30 UTC. The page still reads version 0.4, Under Discussion, voting thread "tbd". No vote date was announced. The bounded fetch cut the page off before the vote section, so the question and required majority were not read.
Limitations
This page summarises a proposal under discussion. Names, semantics and the target version can change before a vote, and nothing here is available in released PHP.
Related: IO hooks, Polling API additions, Time\Instant and Time\Clock.