Async, event-driven and UTF-8 aware console input & output (STDIN, STDOUT) for truly interactive CLI applications, built on top of ReactPHP.
You can use this library to build truly interactive and responsive command
line (CLI) applications, that immediately react when the user types in
a line or hits a certain key. Inspired by ext-readline
, but supports UTF-8
and interleaved I/O (typing while output is being printed), history and
autocomplete support and takes care of proper TTY settings under the hood
without requiring any extensions or special installation.
Table of contents
We invest a lot of time developing, maintaining and updating our awesome open-source projects. You can help us sustain this high-quality of our work by becoming a sponsor on GitHub. Sponsors get numerous benefits in return, see our sponsoring page for details.
Let's take these projects to the next level together! 🚀
Once installed, you can use the following code to present a prompt in a CLI program:
<?php
require __DIR__ . '/vendor/autoload.php';
$stdio = new Clue\React\Stdio\Stdio();
$stdio->setPrompt('Input > ');
$stdio->on('data', function ($line) use ($stdio) {
$line = rtrim($line, "\r\n");
$stdio->write('Your input: ' . $line . PHP_EOL);
if ($line === 'quit') {
$stdio->end();
}
});
See also the examples.
The Stdio
is the main interface to this library.
It is responsible for orchestrating the input and output streams
by registering and forwarding the corresponding events.
$stdio = new Clue\React\Stdio\Stdio();
This class takes an optional LoopInterface|null $loop
parameter that can be used to
pass the event loop instance to use for this object. You can use a null
value
here in order to use the default loop.
This value SHOULD NOT be given unless you're sure you want to explicitly use a
given event loop instance.
See below for waiting for user input and writing output.
The Stdio
class is a well-behaving duplex stream
(implementing ReactPHP's DuplexStreamInterface
) that emits each complete
line as a data
event, including the trailing newline.
The Stdio
is a well-behaving writable stream
implementing ReactPHP's WritableStreamInterface
.
The write($text)
method can be used to print the given text characters to console output.
This is useful if you need more control or want to output individual bytes or binary output:
$stdio->write('hello');
$stdio->write(" world\n");
Because the Stdio
is a well-behaving writable stream,
you can also pipe()
any readable stream into this stream.
$logger->pipe($stdio);
The Stdio
is a well-behaving readable stream
implementing ReactPHP's ReadableStreamInterface
.
It will emit a data
event for every line read from console input.
The event will contain the input buffer as-is, including the trailing newline.
You can register any number of event handlers like this:
$stdio->on('data', function ($line) {
if ($line === "start\n") {
doSomething();
}
});
Note that this class takes care of buffering incomplete lines and will only emit
complete lines.
This means that the line will usually end with the trailing newline character.
If the stream ends without a trailing newline character, it will not be present
in the data
event.
As such, it's usually recommended to remove the trailing newline character
before processing command line input like this:
$stdio->on('data', function ($line) {
$line = rtrim($line, "\r\n");
if ($line === "start") {
doSomething();
}
});
Similarly, if you copy and paste a larger chunk of text, it will properly emit
multiple complete lines with a separate data
event for each line.
Because the Stdio
is a well-behaving readable stream that will emit incoming
data as-is, you can also use this to pipe()
this stream into other writable
streams.
$stdio->pipe($logger);
You can control various aspects of the console input through this interface, so read on..
The prompt will be written at the beginning of the user input line, right before the user input buffer.
The setPrompt($prompt)
method can be used to change the input prompt.
The prompt will be printed to the user input line as-is, so you will likely want to end this with a space:
$stdio->setPrompt('Input: ');
The default input prompt is empty, i.e. the user input line contains only the actual user input buffer. You can restore this behavior by passing an empty prompt:
$stdio->setPrompt('');
The getPrompt()
method can be used to get the current input prompt.
It will return an empty string unless you've set anything else:
assert($stdio->getPrompt() === '');
The echo mode controls how the actual user input buffer will be presented in the user input line.
The setEcho($echo)
method can be used to control the echo mode.
The default is to print the user input buffer as-is.
You can disable printing the user input buffer, e.g. for password prompts.
The user will still be able to type, but will not receive any indication of the current user input buffer.
Please note that this often leads to a bad user experience as users will not even see their cursor position.
Simply pass a boolean false
like this:
$stdio->setEcho(false);
Alternatively, you can also hide the user input buffer by using a replacement character. One replacement character will be printed for each character in the user input buffer. This is useful for password prompts to give users an indicatation that their key presses are registered. This often provides a better user experience and allows users to still control their cursor position. Simply pass a string replacement character likes this:
$stdio->setEcho('*');
To restore the original behavior where every character appears as-is, simply pass a boolean true
:
$stdio->setEcho(true);
Everything the user types will be buffered in the current user input buffer. Once the user hits enter, the user input buffer will be processed and cleared.
The addInput($input)
method can be used to add text to the user input
buffer at the current cursor position.
The given text will be inserted just like the user would type in a text and as
such adjusts the current cursor position accordingly.
The user will be able to delete and/or rewrite the buffer at any time.
Changing the user input buffer can be useful for presenting a preset input to
the user (like the last password attempt).
Simply pass an input string like this:
$stdio->addInput('hello');
The setInput($buffer)
method can be used to control the user input buffer.
The given text will be used to replace the entire current user input buffer
and as such adjusts the current cursor position to the end of the new buffer.
The user will be able to delete and/or rewrite the buffer at any time.
Changing the user input buffer can be useful for presenting a preset input to
the user (like the last password attempt).
Simply pass an input string like this:
$stdio->setInput('lastpass');
The getInput()
method can be used to access the current user input buffer.
This can be useful if you want to append some input behind the current user input buffer.
You can simply access the buffer like this:
$buffer = $stdio->getInput();
By default, users can control their (horizontal) cursor position by using their arrow keys on the keyboard. Also, every character pressed on the keyboard advances the cursor position.
The setMove($toggle)
method can be used to control whether users are allowed to use their arrow keys.
To disable the left and right arrow keys, simply pass a boolean false
like this:
$stdio->setMove(false);
To restore the default behavior where the user can use the left and right arrow keys,
simply pass a boolean true
like this:
$stdio->setMove(true);
The getCursorPosition()
method can be used to access the current cursor position,
measured in number of characters.
This can be useful if you want to get a substring of the current user input buffer.
Simply invoke it like this:
$position = $stdio->getCursorPosition();
The getCursorCell()
method can be used to get the current cursor position,
measured in number of monospace cells.
Most normal characters (plain ASCII and most multi-byte UTF-8 sequences) take a single monospace cell.
However, there are a number of characters that have no visual representation
(and do not take a cell at all) or characters that do not fit within a single
cell (like some Asian glyphs).
This method is mostly useful for calculating the visual cursor position on screen,
but you may also invoke it like this:
$cell = $stdio->getCursorCell();
The moveCursorTo($position)
method can be used to set the current cursor position to the given absolute character position.
For example, to move the cursor to the beginning of the user input buffer, simply call:
$stdio->moveCursorTo(0);
The moveCursorBy($offset)
method can be used to change the cursor position
by the given number of characters relative to the current position.
A positive number will move the cursor to the right - a negative number will move the cursor to the left.
For example, to move the cursor one character to the left, simply call:
$stdio->moveCursorBy(-1);
By default, users can access the history of previous commands by using their UP and DOWN cursor keys on the keyboard. The history will start with an empty state, thus this feature is effectively disabled, as the UP and DOWN cursor keys have no function then.
Note that the history is not maintained automatically. Any input the user submits by hitting enter will not be added to the history automatically. This may seem inconvenient at first, but it actually gives you more control over what (and when) lines should be added to the history. If you want to automatically add everything from the user input to the history, you may want to use something like this:
$stdio->on('data', function ($line) use ($stdio) {
$line = rtrim($line);
$all = $stdio->listHistory();
// skip empty line and duplicate of previous line
if ($line !== '' && $line !== end($all)) {
$stdio->addHistory($line);
}
});
The listHistory(): string[]
method can be used to
return an array with all lines in the history.
This will be an empty array until you add new entries via addHistory()
.
$list = $stdio->listHistory();
assert(count($list) === 0);
The addHistory(string $line): void
method can be used to
add a new line to the (bottom position of the) history list.
A following listHistory()
call will return this line as the last element.
$stdio->addHistory('a');
$stdio->addHistory('b');
$list = $stdio->listHistory();
assert($list === array('a', 'b'));
The clearHistory(): void
method can be used to
clear the complete history list.
A following listHistory()
call will return an empty array until you add new
entries via addHistory()
again.
Note that the history feature will effectively be disabled if the history is
empty, as the UP and DOWN cursor keys have no function then.
$stdio->clearHistory();
$list = $stdio->listHistory();
assert(count($list) === 0);
The limitHistory(?int $limit): void
method can be used to
set a limit of history lines to keep in memory.
By default, only the last 500 lines will be kept in memory and everything else
will be discarded.
You can use an integer value to limit this to the given number of entries or
use null
for an unlimited number (not recommended, because everything is
kept in RAM).
If you set the limit to 0
(int zero), the history will effectively be
disabled, as no lines can be added to or returned from the history list.
If you're building a CLI application, you may also want to use something like
this to obey the HISTSIZE
environment variable:
$limit = getenv('HISTSIZE');
if ($limit === '' || $limit < 0) {
// empty string or negative value means unlimited
$stdio->limitHistory(null);
} elseif ($limit !== false) {
// apply any other value if given
$stdio->limitHistory($limit);
}
There is no such thing as a readHistory()
or writeHistory()
method
because filesystem operations are inherently blocking and thus beyond the scope
of this library.
Using your favorite filesystem API and an appropriate number of addHistory()
or a single listHistory()
call respectively should be fairly straight
forward and is left up as an exercise for the reader of this documentation
(i.e. you).