Re: [RFC] Io\Terminal

From: Date: Fri, 02 Oct 2026 20:38:37 +0000
Subject: Re: [RFC] Io\Terminal
References: 1 2 3 4 5 6 7 8 9 10  Groups: php.internals 
Request: Send a blank email to internals+get-132777@lists.php.net to get a copy of this message
Hi all, Larry, Tim, Bob, thanks for the detailed replies. Tim, thanks also for compiling the branch and checking the POSIX example. > Is that correct? Pratik, are you able to confirm (via tests) that this > would work as a testing approach, including for key and secret reads? For readLine(), yes: exactly as you wrote it, I can confirm it works. I ran: $in = fopen('php://memory', 'w+'); fwrite($in, "first line\nsecond line\n"); rewind($in); $t = Io\Terminal\SystemTerminal::fromStreams($in); var_dump($t->readLine()); var_dump($t->readLine()); var_dump($t->readLine()); and got: string(10) "first line" string(11) "second line" NULL I also checked empty lines, immediate EOF and a final unterminated line. Those behave as specified. For readKey() and readSecret(), ordinary streams like php://memory or php://temp do not work, and are intentionally rejected because those operations require an actual terminal device. For example: $t->readKey(); // Io\Terminal\TerminalException: Failed to read key: input stream is not a terminal On POSIX, Tim's proc_open() + PTY example is exactly the right kind of test for this. It gives the code a real terminal while still letting the test script the input from pure PHP. I also checked Windows because I did not want to assume the POSIX PTY mechanism translated there. It does not directly, since proc_open() on Windows does not provide the same PTY descriptor. I built the current branch on Windows 11 ARM64 and exercised the native console path instead. The PHP side was just: $t = Io\Terminal\SystemTerminal::fromStdio(); var_dump($t->readKey()); A small harness attached to a real Windows console injected KEY_EVENT records into CONIN$ with WriteConsoleInputW(). Injecting Up produced: enum(Io\Terminal\Key::Up) I also checked Down, Left, Right, Enter, Tab, Backspace, Escape, F1, ordinary characters, repeat counts and Unicode including a UTF-16 surrogate pair. readSecret() was exercised through the same native console path for normal input, empty input, backspace, Unicode, Escape/Ctrl+C cancellation (which throws TerminalException) and zero-timeout return (which returns null). readLine() was exercised through the native ReadConsoleW() path as well. The ARM64 build needed an unrelated local Zend SIMD compiler workaround, but I did not change Io\Terminal for these tests. So the direct answer is yes for line input, but with a platform distinction for interactive keys. Ordinary stream behavior can be scripted with normal PHP streams. Terminal-specific behavior needs a real terminal. On POSIX that is conveniently available through a PTY in pure PHP, as Tim showed. On Windows the native implementation is testable too, but the equivalent test currently needs a real console/native helper rather than the same PTY recipe. That means Tim and Bob's point about the stream/terminal being the underlying I/O boundary holds, while Larry's concern about a straightforward cross-platform testing seam is still relevant for Windows code that directly consumes readKey(). > Although, it occurs to me while typing the above, the Terminal accepts > an output stream, but doesn't appear to have any output API. When is > the output stream even used? Should it be removed, or a print() (or > similar) API added? I checked this in the implementation. The output stream is not unused. getSize() queries the output side for terminal dimensions. readKey() also checks the output side for size changes so it can return Key::Resize, and on Windows a WINDOW_BUFFER_SIZE_EVENT causes the output side to be queried again for the current dimensions. So I don't think the output stream should be removed. I also don't think we need print() or write() just to justify it; actual application output can continue to use fwrite()/echo and normal stream APIs. On the interface question, Tim's objections to the current Terminal/ModeToken shape still make sense to me, especially the ModeToken case where a userland token can satisfy the interface but cannot actually be restored by the native terminal. At the same time, after checking Windows I don't think I can honestly say that php://memory or stream injection alone makes the interface unnecessary everywhere. It clearly solves readLine(), and the PTY gives us a good POSIX integration seam, but Windows userland code directly consuming readKey() does not have the same pure-PHP PTY seam today. I have not changed the RFC or implementation yet. I would like to settle this point before making another API change, so that the RFC can go into the next stage with this part resolved rather than carrying the disagreement forward. The remaining question for me is what testing seam we want cross-platform userland code which directly consumes readKey() to have, without keeping the ModeToken problem Tim pointed out. Best regards, Pratik

« previous php.internals (#132777) next »