====== PHP RFC: Io\Terminal ====== * Version: 0.1 * Date: 2026-09-27 * Author: Pratik Bhujel, prateekbhujelpb@gmail.com * Status: Draft * Target Version: PHP 8.7 * Implementation: https://github.com/php/php-src/pull/23941 * Discussion thread: tbd * Voting thread: tbd ===== Introduction ===== PHP does not currently provide a cross-platform API for basic terminal operations such as querying the terminal size, switching the terminal into raw mode, reading individual keys, or reading secret input without echo. Userland libraries generally implement these features using platform-specific workarounds. On POSIX systems this often involves invoking tools such as ''stty'', while Windows requires the Win32 Console API. This RFC proposes a small native terminal API in the ''Io\Terminal'' namespace. getSize(); if ($size !== false) { printf("%dx%d\n", $size->cols, $size->rows); } $mode = $terminal->enableRawMode(); try { $key = $terminal->readKey(Duration::fromSeconds(5)); if ($key === Key::Escape) { echo "Escape pressed\n"; } } finally { if ($mode !== false) { $terminal->restoreMode($mode); } } ===== Proposal ===== The RFC adds an object-oriented terminal API under ''Io\Terminal''. The proposed API is: ''Terminal'' and ''ModeToken'' are not serializable. ==== Creating a terminal session ==== ''Terminal::create()'' creates a terminal session using the process standard input and standard output. ''Terminal::fromStreams()'' creates a terminal session using an explicit input stream and an optional output stream. If the output stream is omitted, the input stream is also used as the output stream. The constructor is private. Terminal instances are created using these named constructors. ==== Terminal size ==== ''Terminal::getSize()'' queries the size of the terminal associated with the output side of the session. On success it returns a ''TerminalSize'' object containing the number of columns and rows. It returns ''false'' if a native terminal size cannot be obtained. No fallback to the ''COLUMNS'' or ''LINES'' environment variables is performed by this API. ==== Raw mode ==== ''Terminal::enableRawMode()'' enables raw input mode for the session input and returns a ''ModeToken''. It returns ''false'' if raw mode cannot be enabled. ''Terminal::restoreMode()'' restores a saved terminal mode. A token can be passed explicitly, or the method can be called without an argument to restore the active mode owned by the ''Terminal'' instance. ''ModeToken'' is an opaque object. An active token restores its saved terminal mode when it is destroyed. A ''Terminal'' instance also restores an active mode it owns when the object is destroyed. Active modes are restored during normal request shutdown as well. These restoration mechanisms are intended to avoid leaving a terminal in raw or no-echo mode after ordinary PHP control flow exits. They cannot provide a guarantee for process termination that bypasses PHP shutdown entirely. ==== Reading keys ==== ''Terminal::readKey()'' reads one logical key from the session input. Recognized special keys are returned as ''Io\Terminal\Key'' enum cases. Printable input is returned as a string. Input that does not map to a known enum case is also returned as a string rather than discarded. ''Key::Resize'' is returned when a terminal resize event is observed. The optional ''$timeout'' controls how long the method waits for input. A ''null'' timeout waits indefinitely. A zero duration performs a non-blocking poll. On POSIX, ''$sequenceTimeout'' controls the short wait used while reading multi-byte input and terminal escape sequences. If it is omitted, the implementation uses a 25 ms sequence timeout. Windows receives structured console input events and does not use this value. Both timeout parameters use ''Time\Duration''. Negative durations result in a ''ValueError''. ==== Reading secret input ==== ''Terminal::readSecret()'' reads input without echoing the entered characters. Enter completes the input. Backspace removes the previous character. Escape, Ctrl+C, and Ctrl+D cancel the read. If the read cannot be completed, or if the terminal mode cannot be restored, the method throws ''Io\Terminal\TerminalException''. ''TerminalException'' extends ''Io\IoException''. ===== Motivation ===== Terminal handling is inherently platform-specific. POSIX systems expose terminal functionality through APIs such as ''termios'' and ''ioctl'', while Windows uses the Win32 Console API. Without native support, PHP libraries need external processes, FFI, or other platform-specific workarounds for these operations. The API proposed here is based on experience from the ext-terminal extension and from integrating that extension with Symfony Console/TUI. The core proposal is intentionally smaller than the complete ext-terminal API. ===== Backward Incompatible Changes ===== No existing PHP API is changed. The RFC introduces the following names: * ''Io\Terminal\Terminal'' * ''Io\Terminal\TerminalSize'' * ''Io\Terminal\ModeToken'' * ''Io\Terminal\TerminalException'' * ''Io\Terminal\Key'' Userland code declaring one of these exact names would conflict with the new internal symbol. ===== Proposed PHP Version(s) ===== PHP 8.7. ===== RFC Impact ===== ==== To the Ecosystem ==== CLI libraries may use the native API when it is available and retain their existing fallbacks for older PHP versions. The ext-terminal extension can continue to serve as a backport and as a place to experiment with functionality that is outside this RFC. ==== To Existing Extensions ==== The implementation is part of ''ext/standard''. No existing extension API is removed or changed. ==== To SAPIs ==== The main use case is CLI and other environments attached to a terminal. The API itself is available independently of SAPI. Operations that require a terminal return ''false'' or throw ''TerminalException'' as described above when the associated stream is not a usable terminal. ===== Open Issues ===== None currently. ===== Future Scope ===== The following functionality exists in the reference extension or has been explored separately, but is not proposed by this RFC: * lower-level terminal input events * ANSI support detection * terminal color-depth detection * setting the terminal title * emitting a terminal bell * environment-variable based terminal-size fallbacks These can be considered separately if there is sufficient need and the API semantics are clear. ===== Voting Choices ===== The primary vote will be: ''Implement the Io\Terminal API as described in this RFC?'' The available choices will be Yes, No, and Abstain. Acceptance requires a 2/3 majority of Yes votes over No votes. ===== Patches and Tests ===== Implementation: https://github.com/php/php-src/pull/23941 Reference extension: https://github.com/prateekbhujel/php-terminal The implementation includes PHPT coverage for terminal construction, terminal sizing, raw mode and restoration, key input, escape sequences, timeout validation, and secret input. ===== Implementation ===== Not implemented. ===== References ===== Previous discussion: * https://externals.io/message/131004 * https://externals.io/message/132523 * https://externals.io/message/132526 * https://externals.io/message/132530 Related work: * https://github.com/prateekbhujel/php-terminal * https://github.com/php/php-src/pull/23941 * https://github.com/symfony/symfony/pull/66173 * https://github.com/symfony/symfony/pull/66271 * https://github.com/symfony/symfony/pull/66272 ===== Rejected Features ===== Earlier iterations considered a larger API. The following are not part of this RFC: * exposing the same operations through both procedural functions and object methods * separate stdin, stdout, and stderr terminal objects instead of a single terminal session * a public constructor in addition to named constructors * separate ''getWidth()'' and ''getHeight()'' methods in addition to ''getSize()'' * compatibility-only APIs from older ext-terminal releases * the complete ext-terminal 1.x API ===== Changelog ===== * 2026-09-27: Initial draft.