PHP has no built-in way to ask the terminal how big it is, read a single keypress, or take a password without echoing it back. CLI libraries work around that today by calling stty on Linux and macOS, reaching for the Win32 Console API on Windows, or going through FFI. Pratik Bhujel's RFC adds a small native API for those jobs in a new Io\Terminal namespace that runs on both POSIX systems and Windows.

How it works

You get a session from Terminal::create(), which uses standard input and output, or from Terminal::fromStreams() if you want to pass your own streams. Here is the RFC's example, trimmed a little:

use Io\Terminal\Key;
use Io\Terminal\Terminal;
use Time\Duration;

$terminal = Terminal::create();

$size = $terminal->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);
    }
}

getSize() returns a TerminalSize with cols and rows, or false when there's no real terminal. It doesn't fall back to the COLUMNS or LINES environment variables.

enableRawMode() hands back a ModeToken. If you forget to restore it, PHP restores the mode when the token or the Terminal object is destroyed, and again at request shutdown. That covers the classic bug where a script crashes and leaves your shell in raw mode, though it can't help if the process dies without running PHP's shutdown.

readKey() returns a Key enum case for arrows, Enter, Escape, Tab, F1 through F12, a Resize event and a few more. Anything else comes back as a string. Timeouts take Time\Duration objects, and a zero duration does a non-blocking poll.

For passwords, readSecret() reads input without echoing it:

$password = Terminal::create()->readSecret();

Enter finishes the read. Escape, Ctrl+C or Ctrl+D cancel it, and the method throws Io\Terminal\TerminalException.

What it means for existing code

Nothing changes for existing code. The RFC adds five new names under Io\Terminal, and userland code that already declares one of those exact names would conflict. CLI libraries can use the native API when it's there and keep their current fallbacks for older PHP versions. The author's ext-terminal extension, installable through PIE, stays around as a backport.

Where it stands

Pratik Bhujel opened the internals discussion on September 27, 2026, and the RFC targets PHP 8.7. The RFC still lists open questions: mode ownership when several descriptors point at the same terminal, timeout handling for partial escape sequences, how it works with pcntl and SIGWINCH, and test coverage on real terminals. A draft implementation is up for review in php-src PR #23941. No vote is scheduled yet.