[PHP-DEV] [Discussion] Native terminal helpers for PHP CLI

Hi internals,

I have been experimenting with a small PHP extension for native terminal
helpers:

https://github.com/prateekbhujel/php-terminal

The goal is to expose a small cross-platform CLI terminal layer for the
pieces that are currently awkward to normalize in userland, especially on
Windows:

  • checking whether stdin/stdout/stderr are TTYs
  • reading terminal size
  • enabling/restoring raw mode safely
  • reading a single key with normalized names
  • writing directly to stdout/stderr

This started from a practical issue with prompt-style PHP CLIs. Laravel
Prompts, for example, currently cannot use its normal interactive prompt
flow on Windows because the backend depends on Unix-style terminal
behavior. I opened a proof-of-concept integration here:

https://github.com/laravel/prompts/pull/242

The extension is still alpha, and I am not proposing it for core right
now. I mainly want feedback on whether this API shape makes sense from a
PHP CLI/runtime point of view, and whether this is better kept as
PECL/ecosystem work first.

Initial release with Windows builds:

https://github.com/prateekbhujel/php-terminal/releases/tag/v0.1.0

If this would be better discussed on internals-win first, I am happy to
move it there. I am mainly looking for feedback on API shape, naming,
Windows behavior, and missing primitives before taking it further.

Thanks,
Pratik

On Sun, 24 May 2026, Pratik Bhujel wrote:

The goal is to expose a small cross-platform CLI terminal layer for
the pieces that are currently awkward to normalize in userland,
especially on Windows:

- checking whether stdin/stdout/stderr are TTYs
- reading terminal size
- enabling/restoring raw mode safely
- reading a single key with normalized names
- writing directly to stdout/stderr

I think that this is something useful to have.

The extension is still alpha, and I am not proposing it for core right
now. I mainly want feedback on whether this API shape makes sense from
a PHP CLI/runtime point of view, and whether this is better kept as
PECL/ecosystem work first.

I have had a look at the API, and from my point of view, I think I would
like to see, with in mind a possible introduction into PHP core:

- All functions to be part of a Terminal\Terminal class — we have
  guidelines for this in place: policies/coding-standards-and-naming.rst at main · php/policies · GitHub

- The getBackend() method should return an enum, as there are
  (currently) only two possible values: windows, and posix.

- Reading a key returns either a string containing a character, or a
  sequence of characters describing an action (up, down, etc). I think,
  because both of these are strings, this is awkward. Perhaps it would
  be better for actual characters to return strings, and again, the
  special key-presses to be case of an Enum.

  In addition, would it return Unicode graphemes (think emojis), or just
  code points?

- The stream names (STDOUT, STDIN, and STDERR) again should probably an
  Enum.

cheers,
Derick

--
https://derickrethans.nl | https://xdebug.org | https://xdebug.cloud
Author of Xdebug. Like it? Consider supporting me: Xdebug: Support
mastodon: @derickr@phpc.social @xdebug@phpc.social

Hi Derick,

Thanks, that makes sense.

I moved the package in that direction now. The current v0.3.0 API uses Terminal\Terminal with camelCase methods, plus Terminal\Backend, Terminal\Stream, and Terminal\Key enums:

https://github.com/prateekbhujel/php-terminal/releases/tag/v0.3.0

I also removed the old procedural API instead of keeping compatibility around it. Since this is still pre-1.0 and not something people should be depending on yet, it felt better to make the shape cleaner now.

For readKey(), special keys now return Terminal\Key cases, while printable input still returns a string.

For Unicode, it currently returns the next encoded code point from the terminal, not a full grapheme cluster. That seemed like the clearer low-level primitive to me, but I am happy to adjust that if core would prefer a different contract.

Thanks again for looking at it.

Pratik Bhujel

On Tue, Jun 2, 2026 at 2:27 PM Derick Rethans <derick@php.net> wrote:

On Sun, 24 May 2026, Pratik Bhujel wrote:

The goal is to expose a small cross-platform CLI terminal layer for
the pieces that are currently awkward to normalize in userland,
especially on Windows:

  • checking whether stdin/stdout/stderr are TTYs
  • reading terminal size
  • enabling/restoring raw mode safely
  • reading a single key with normalized names
  • writing directly to stdout/stderr

I think that this is something useful to have.

The extension is still alpha, and I am not proposing it for core right
now. I mainly want feedback on whether this API shape makes sense from
a PHP CLI/runtime point of view, and whether this is better kept as
PECL/ecosystem work first.

I have had a look at the API, and from my point of view, I think I would
like to see, with in mind a possible introduction into PHP core:

  • All functions to be part of a Terminal\Terminal class — we have
    guidelines for this in place: https://github.com/php/policies/blob/main/coding-standards-and-naming.rst#bundled-extensions

  • The getBackend() method should return an enum, as there are
    (currently) only two possible values: windows, and posix.

  • Reading a key returns either a string containing a character, or a
    sequence of characters describing an action (up, down, etc). I think,
    because both of these are strings, this is awkward. Perhaps it would
    be better for actual characters to return strings, and again, the
    special key-presses to be case of an Enum.

In addition, would it return Unicode graphemes (think emojis), or just
code points?

  • The stream names (STDOUT, STDIN, and STDERR) again should probably an
    Enum.

cheers,
Derick


https://derickrethans.nl | https://xdebug.org | https://xdebug.cloud
Author of Xdebug. Like it? Consider supporting me: https://xdebug.org/support
mastodon: @derickr@phpc.social @xdebug@phpc.social

Hi Derick, hi internals,

Small update from my side: I pushed this further based on the feedback here.

The current release is v0.4.1 now:
https://github.com/prateekbhujel/php-terminal/releases/tag/v0.4.1

The API is now fully on the Terminal\Terminal class + enums shape. Raw mode restore uses an opaque Terminal\ModeToken object, special keys return Terminal\Key cases, printable input returns strings, and resize is surfaced as Terminal\Key::Resize on both POSIX and Windows.

I also documented the current key scope more clearly: code points rather than grapheme clusters for now, no F1-F12/modifier normalization yet, and the POSIX sequence timeout defaults to 25ms.

At this point I am mainly wondering what the right next step is: keep hardening it as a PECL/ecosystem package first, or is there anything in the current API shape that would already be a blocker if this ever moved closer to core?

Thanks,
Pratik

On Tue, Jun 2, 2026 at 3:42 PM Pratik Bhujel <prateekbhujelpb@gmail.com> wrote:

Hi Derick,

Thanks, that makes sense.

I moved the package in that direction now. The current v0.3.0 API uses Terminal\Terminal with camelCase methods, plus Terminal\Backend, Terminal\Stream, and Terminal\Key enums:

https://github.com/prateekbhujel/php-terminal/releases/tag/v0.3.0

I also removed the old procedural API instead of keeping compatibility around it. Since this is still pre-1.0 and not something people should be depending on yet, it felt better to make the shape cleaner now.

For readKey(), special keys now return Terminal\Key cases, while printable input still returns a string.

For Unicode, it currently returns the next encoded code point from the terminal, not a full grapheme cluster. That seemed like the clearer low-level primitive to me, but I am happy to adjust that if core would prefer a different contract.

Thanks again for looking at it.

Pratik Bhujel

On Tue, Jun 2, 2026 at 2:27 PM Derick Rethans <derick@php.net> wrote:

On Sun, 24 May 2026, Pratik Bhujel wrote:

The goal is to expose a small cross-platform CLI terminal layer for
the pieces that are currently awkward to normalize in userland,
especially on Windows:

  • checking whether stdin/stdout/stderr are TTYs
  • reading terminal size
  • enabling/restoring raw mode safely
  • reading a single key with normalized names
  • writing directly to stdout/stderr

I think that this is something useful to have.

The extension is still alpha, and I am not proposing it for core right
now. I mainly want feedback on whether this API shape makes sense from
a PHP CLI/runtime point of view, and whether this is better kept as
PECL/ecosystem work first.

I have had a look at the API, and from my point of view, I think I would
like to see, with in mind a possible introduction into PHP core:

  • All functions to be part of a Terminal\Terminal class — we have
    guidelines for this in place: https://github.com/php/policies/blob/main/coding-standards-and-naming.rst#bundled-extensions

  • The getBackend() method should return an enum, as there are
    (currently) only two possible values: windows, and posix.

  • Reading a key returns either a string containing a character, or a
    sequence of characters describing an action (up, down, etc). I think,
    because both of these are strings, this is awkward. Perhaps it would
    be better for actual characters to return strings, and again, the
    special key-presses to be case of an Enum.

In addition, would it return Unicode graphemes (think emojis), or just
code points?

  • The stream names (STDOUT, STDIN, and STDERR) again should probably an
    Enum.

cheers,
Derick


https://derickrethans.nl | https://xdebug.org | https://xdebug.cloud
Author of Xdebug. Like it? Consider supporting me: https://xdebug.org/support
mastodon: @derickr@phpc.social @xdebug@phpc.social