Re: [RFC] Io\Terminal

From: Date: Fri, 02 Oct 2026 16:20:22 +0000
Subject: Re: [RFC] Io\Terminal
References: 1 2 3 4 5 6 7 8  Groups: php.internals 
Request: Send a blank email to internals+get-132773@lists.php.net to get a copy of this message
On Fri, Oct 2, 2026, at 10:59 AM, Tim Düsterhus wrote: > Hi > > On 2026-10-02 16:19, Larry Garfield wrote: >> My main concern is being able to mock the terminal in order to >> effectively test code that uses the terminal, without putting a >> proprietary thin wrapper around it (which largely defeats the purpose >> of having a good API in core). > > It is not clear to me what you mean by “proprietary” here. As I had > mentioned in my email, the RFC’s own PR is tested against processes > spawned using proc_open() with > pty descriptors. The tests are > naturally BSD-licensed, just like PHP itself is (since PHP 8.6). I didn't mean proprietary as in license. I mean, for example, a SymfonyTerminal that wraps a Terminal so that SymfonyTerminal can be mocked. Which of course is different than LaravelTerminal. That would be a bad place to end up, and we should avoid that. ("Wrap 3rd party code so you can mock it" is a common recommendation, though as in this case it can lead to other problems.) > In your test you would create a process that emits scripted output > (possibly in response to some input), just like you would in the “mocked > interface implementation” and then pass the PTY input/output streams to > whatever you want to test, which will then call > (System)Terminal::fromStreams() using them as parameters. The > logic > under test reads inputs from the Terminal instance and writes output > using fwrite(). The subprocess will also enable you to properly > model > concurrency, delay and timing in IO processing. Terminal interactions in > the real world are not synchronous either: The terminal logic relies on > timing to distinguish escape sequences from individual characters > (that's what the $sequenceTimeout is for) and the user > might already > provide additional input while your application is still busy rendering > output and not yet expecting additional data. > >> Interfaces are the standard way of doing that. If you have a >> suggestion for a better way, I'm happy to see it. > > My email included 5 arguments as to why an interface is the wrong design > here. Do you plan to engage with those? No, because I am not advocating for interfaces. I am advocating for a clean and obvious mocking/testing mechanism, for which interfaces are a common solution. I am not wedded to interfaces as the solution, just that there is a reliable one that is self-evident (and/or documented). That is, my invitation to suggest a better way was in no way factious or snarky. If I understand what you and Bob (thanks Bob) are suggesting, one would do something like: $in = fopen('php://memory'); $out = fopen('php://memory'); fputs($in, "first line\n"); fputs($in, "second line\n"); rewind($in); $t = Terminal::fromStreams($in, $out); $line = $t->readLine(); assert($line === 'first line'); $line = $t->readLine(); assert($line === 'second line'); 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? If so, then I agree the interfaces become unnecessary, and the above should be included in the RFC as the recommended testing approach. 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? --Larry Garfield

« previous php.internals (#132773) next »