Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Term Laravel Package

php-tui/term

php-tui/term is a PHP library for terminal control and TUI development, with ANSI escape sequences, cursor movement, text styling, screen management, and input handling. Build responsive command-line interfaces and interactive CLI tools without heavy dependencies.

Deep Wiki
Context7

Getting Started

php-tui/term is the low-level I/O layer beneath the larger php-tui/php-tui framework. It gives you raw terminal control: ANSI escape sequences, cursor movement, colors, raw stdin reading (keys/mouse), terminal size, and alternate-screen handling. It is not a widget/framework library — you render strings and sequences yourself.

Install and check the floor:

composer require php-tui/term

It targets PHP 8.1+ and expects a POSIX-ish terminal. There is no Laravel service provider shipped; you integrate it yourself.

Where to look first, in order:

  1. PhpTui\Term\TerminalInterface — the whole contract. Every capability you get day-to-day is a method here (execute(), drainEvents(), size(), enableRawMode()).
  2. PhpTui\Term\Terminal — the concrete implementation you instantiate.
  3. PhpTui\Term\Action\* — the command objects you pass to execute() (PrintText, MoveCursor, Clear, SetForegroundColor, SetBackgroundColor, SetModifier, SetCursorVisibility, EnableRawMode, …).
  4. PhpTui\Term\Event\* — KeyEvent, MouseEvent, resize events returned from drainEvents().

The smallest useful thing — clear the screen, print colored text, restore:

use PhpTui\Term\Terminal;
use PhpTui\Term\Action\{Clear, ClearType, MoveCursor, SetForegroundColor, PrintText, ResetColor};
use PhpTui\Term\Color;

$term = Terminal::new();
$term->execute(
    Clear::new(ClearType::All),
    MoveCursor::position(2, 2),
    SetForegroundColor::new(Color::Green),
    PrintText::new('Hello from raw terminal land'),
    ResetColor::new(),
);

First real use case for a Laravel dev: an Artisan command that renders a live, self-updating dashboard (queue depth, cache hits, whatever) instead of dumping a line per tick, or a command that reads single keypresses without requiring Enter — something Laravel Prompts and Symfony Console cannot do.

class WatchCommand extends Command
{
    protected $signature = 'watch:queue';

    public function handle(): int
    {
        $term = Terminal::new();
        $term->enableRawMode();

        try {
            while (true) {
                $term->execute(
                    Clear::new(ClearType::All),
                    MoveCursor::position(1, 1),
                    PrintText::new('Queue: ' . Queue::size() . '  (q to quit)'),
                );

                foreach ($term->drainEvents() as $event) {
                    if ($event instanceof KeyEvent && $event->key() === 'q') {
                        return self::SUCCESS;
                    }
                }

                usleep(250_000);
            }
        } finally {
            $term->disableRawMode();
        }
    }
}

Once that loop feels natural, the rest of the package is just more commands and events.

Implementation Patterns

Wrap the terminal once, inject it. Do not call Terminal::new() scattered through your code. Bind TerminalInterface in a service provider (or a small TerminalManager) so commands and services can receive a fake in tests:

$this->app->singleton(TerminalInterface::class, fn () => Terminal::new());

Double-buffer your own screen. php-tui/term gives you primitives, not a diff engine. For anything animated, keep a $previous array of rendered lines and only emit MoveCursor + PrintText for lines that changed. Full Clear::new() every tick is visually fine at 1–4 Hz, but flickers badly at 30 Hz. If you want a proper diffing renderer, step up to php-tui/php-tui and use its Screen/Buffer abstractions rather than reinventing them.

Event loop shape. The idiomatic pattern is poll → drainEvents → render → sleep. drainEvents() is non-blocking; combine it with a tick interval rather than busy-looping. If you need blocking reads, you will instead read from the input stream in a tighter loop — but a 100–250 ms tick is what most dashboards actually want.

Compose commands fluently. Every render pass is a batch of actions passed to one execute() call. That reduces syscalls and, more importantly, keeps rendering atomic — no half-drawn frame where a cursor move has landed but the text has not.

Fallback for non-interactive contexts. Laravel commands run under CI, in php artisan pipelines, under Horizon, and in Docker exec without a TTY. Gate on:

if (! stream_isatty(STDOUT)) {
    // plain $this->line() output path
}

Treat the pretty path as an enhancement, never the only path. Same for --no-interaction and -v verbosity flags — respect them.

Route diagnostics away from stdout. Once you own stdout with cursor positioning, any other write — dump(), $this->line(), Log::info() configured to single, PHP warnings, deprecations — corrupts the frame. Either log to stderr/file explicitly, or capture ob_start() around the render and discard accidental output. A common trick is fwrite(STDERR, ...) for debug output during development.

Sanitize before you print. Any string originating from the DB or an external API must be stripped of \e / \x1b and control characters before it reaches PrintText. One poisoned record with an escape sequence can move your cursor, clear the screen, or change colors mid-frame.

Signals and cleanup. Raw mode left on after a crash is the single most annoying failure mode — the terminal echoes nothing and the shell looks broken (stty sane recovers it). Register pcntl_signal(SIGINT, ...) and SIGTERM handlers that disable raw mode, show the cursor, and exit; and always use try/finally around the raw-mode block.

Testing. Swap in a fake TerminalInterface that records executed actions into an array, and assert on the action stream rather than on rendered text. Keep raw mode off in tests entirely — CI has no TTY.

Gotchas and Tips

  • Raw mode is the classic footgun. Wrap it in try/finally and handle SIGINT/SIGTERM. If you kill your terminal session while developing, reset or stty sane restores it.
  • Windows is a second-class citizen. ANSI works in Windows Terminal, but you will hit problems with older cmd.exe, redirection, and raw key input. If Windows support matters, feature-detect and fall back to Symfony Console output.
  • Unicode width will break your layout. strlen and even mb_strlen are wrong for East Asian wide characters and emoji. You need wcwidth semantics to align columns; check whether the version you pin handles it, or normalize/limit input. This is the most common source of "my borders don't line up" bugs.
  • Non-TTY terminals misbehave. Terminal::new() on a pipe or in CI can fail or write garbage into your log stream. Always guard on stream_isatty(STDOUT) before constructing.
  • Resize events are not automatic everywhere. Rely on polling $term->size() at tick boundaries, or handle SIGWINCH if pcntl is available. Assume the terminal can change size between any two frames and re-layout from size() each tick.
  • Always restore the cursor. Toggle SetCursorVisibility/ResetCursor and restore the alternate screen in your finally. Forgetting leaves the user with an invisible cursor after the command exits.
  • Version and maintenance risk. Last release was December 2023, ~45 stars, no Laravel integration. Pin composer.lock and treat it as a leaf dependency: keep the surface you touch small (a TerminalManager wrapper) so you can swap it out or fork it if the project stalls.
  • Don't run it in HTTP requests or queue workers. This is CLI-only. Under Octane, Horizon, or a queued job, stdout is not a terminal and any raw-mode call is at best a no-op and at worst an error. Restrict usage to Artisan commands and standalone scripts.
  • usleep vs hrtime. For anything with a target frame rate, measure with hrtime(true) and sleep the remainder of the frame budget — usleep() drifts and will make your 10 FPS dashboard into 7 FPS.
  • Escape hatch for the hard parts. When you find yourself writing a diffing renderer, text wrapping, or a widget layout engine on top of term, stop: use php-tui/php-tui (which depends on this package) or fall back to Symfony Console + Laravel Prompts. php-tui/term is the right tool only when you need direct control over the byte stream.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
robertmarney/lara-hierarchial-collections
simonschaufi/rule-doc-generator
emissaoja/emissaoja
blackbricksoftware/gravityforms-civicrm-form-processor
al-rahabi/fancy-crud
jinas/dhivatar-component
mltstephane/laravel-quality
bernskiold/laravel-talentlms
bernskioldmedia/laravel-form-architect