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

Console Laravel Package

hoa/console

Hoa\Console is a PHP library for building command-line applications. It provides terminal abstraction, cursor and window control, readline-style input, process management, option parsing, and I/O helpers, making rich interactive CLI tools easier to develop and maintain.

View on GitHub
Deep Wiki
Context7

Getting Started

hoa/console is a low-level terminal toolkit (readline, cursor control, tty introspection, process spawning) — not an Artisan/Symfony Console replacement. It is archived (last release May 2017), so treat it as a legacy dependency you inherit or opt into deliberately, not something to build a new Laravel app around.

Install and confirm it actually loads on your PHP version before writing any code:

composer require hoa/console:^3.17
php -r "require 'vendor/autoload.php'; var_dump(class_exists('Hoa\Console\Console'));"

Where to look first, in order:

  1. Hoa\Console\Console::getInstance() — the singleton giving you getInput(), getOutput(), getErrorOutput().
  2. Hoa\Console\Readline\Readline — the reason most people pull this in (history, autocompletion, multi-line input).
  3. Hoa\Console\Cursor and Hoa\Console\Window — ANSI cursor movement and terminal sizing.
  4. Hoa\Console\Chrome\Text::columnize() — cheap column/table formatting.
  5. Hoa\Console\Processus — the old process runner; you almost certainly want Illuminate\Support\Facades\Process instead.

First use case — a genuinely interactive prompt inside an Artisan command, which Laravel's $this->ask() does poorly:

use Hoa\Console\Readline\Readline;

public function handle()
{
    if (! $this->input->isInteractive()) {
        $this->error('Requires an interactive terminal.');

        return self::FAILURE;
    }

    $readline = new Readline();
    $readline->addMapping("\t", fn ($line) => /* completion logic */ $line);
    $readline->setAutocompleter(new MyCompleter());

    $answer = $readline->readLine('Project name> ');
}

Reality check before you start: verify it runs on your PHP version (8.x is not supported upstream) and that registering it doesn't clash with Symfony Console's own terminal handling. If the smoke test throws deprecations or fatals, stop — the package is unmaintained and patches are on you.

Implementation Patterns

Isolate it behind a small adapter. Never scatter Hoa\Console\* calls through commands; wrap them in an injectable class so you can swap in laravel/prompts or Symfony helpers later, and so tests can fake it.

final class Terminal
{
    public function isInteractive(): bool
    {
        return Hoa\Console\Tty::isatty(STDOUT)
            && app()->runningInConsole()
            && ! app()->runningUnitTests();
    }

    public function prompt(string $label): ?string { /* Readline */ }
    public function clearLine(): void { /* Cursor::clear('line') */ }
}

Gate every escape sequence behind a decoration check. Hoa emits raw ANSI with no awareness of Laravel's --no-ansi, NO_COLOR, or redirected output. Build one predicate and reuse it:

$decorated = $this->output->isDecorated() && $this->terminal->isInteractive();

Use Readline only for single, foreground, human-facing prompts. It blocks on STDIN, maintains its own history file, and takes over the terminal. That makes it a poor fit inside queued jobs, artisan invoked from another process, PHPUnit, or anything piped (echo "x" | artisan ...), where it will hang or read garbage. Guard with isInteractive() and provide a --non-interactive flag path everywhere.

Cursor/Window tricks belong in a "live" render loop, not in normal output. Spinners, in-place progress counters, and resizing-aware dashboards work like this:

use Hoa\Console\Cursor;
use Hoa\Console\Window;

$width  = Window::getSize()['x'] ?? 80;
$height = Window::getSize()['y'] ?? 24;

Cursor::save();
foreach ($items as $i => $item) {
    Cursor::move('up');         // reposition
    Cursor::clear('line');
    echo substr($item, 0, $width);
    usleep(50_000);
}
Cursor::restore();

Note the ?? 80 — Window::getSize() returns null/empty when there's no real tty (CI, Docker without -t, IDE run panels), so always default.

Prefer Chrome\Text::columnize() only when Symfony's $this->table() is inconvenient — e.g. when you need a raw string for a Log::info block or an email body rather than console output. Symfony's table helper handles borders, wrapping, and styling better, and it respects --no-ansi automatically.

Do not use Processus in new code. Laravel 10+ ships the Process facade with timeouts, retries, output streaming, and fakes. If a legacy code path is already using Processus, migrate it rather than extending it — Hoa's process handling predates proc_open argument-array safety and is a quoting/injection risk on Windows.

Integration tip — service provider. Because Hoa packages register stream wrappers and autoload files via Composer files, bind your adapter as a singleton in a provider and resolve lazily. Doing the Console::getInstance() bootstrap in register() can capture STDIN before Artisan has finished setting up its IO.

Gotchas and Tips

  • Abandoned and PHP-version fragile. Last release 2017. On PHP 8 expect deprecation notices from hoa/consistency and hoa/stream, and possibly fatals from reflection/each()-era code. Test in a throwaway container first; if it errors, drop it rather than patching vendor code.
  • It fights Symfony Console for the terminal. Artisan already owns STDOUT styling, cursor state, and Readline-ish input via symfony/console. Mixing both in one command produces duplicated prompts, eaten keystrokes, or leftover escape codes. Pick one per command.
  • Everything is global state. Console::getInstance(), Cursor, and Readline all talk to process-wide STDIN/STDOUT. That makes them untestable and unsafe under parallel test runs or artisan invoked recursively. Your adapter should be the only seam that touches them.
  • No tty, no party. Tty::isatty(), Window::getSize(), and interactive Readline all fail or degrade silently when output is piped, in CI, in Docker without -it, or inside phpunit. Always provide a non-interactive branch and default terminal dimensions.
  • NO_COLOR / --no-ansi are ignored. Hoa has no concept of decoration negotiation. Honour $this->output->isDecorated(), $this->option('no-ansi'), and getenv('NO_COLOR') yourself before emitting any Cursor::colorize() output — otherwise your logs and CI artifacts fill with \e[31m.
  • Debugging escape codes: pipe through cat -v to see what's actually emitted, and script -q /dev/null artisan cmd to fake a tty. Leftover artifacts almost always mean a missing Cursor::clear() or an early exit mid-render-loop — wrap render loops in try/finally with Cursor::restore().
  • Config quirks: no config file exists — behaviour is driven entirely by the environment (ext-readline, ext-posix, ext-pcntl, TERM). TERM=dumb is not respected; you must check it yourself.
  • Extension points are thin. The only real seams are Readline::addMapping() / setAutocompleter() and the hoa:// stream wrappers. There's no DI, no events worth hooking, and no upstream to send fixes to — treat the API as frozen.
  • Autoload side effects. Hoa packages register hoa:// wrappers at autoload time; in rare cases that collides with other stream-wrapper users (flysystem adapters, phar handling). If you see bizarre include failures after adding it, suspect this first.
  • Bottom line for a Laravel codebase: use it only for the interactive Readline experience or legacy code you can't yet remove. For prompts use laravel/prompts; for colours and progress use Symfony's Style/ProgressBar; for tables use $this->table(); for processes use the Process facade. Budget for a removal, not an adoption.
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.
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator
equipc/laravel-email-database-log
adedsec/nova-translated-labels
wazza/laravel-db-encryption
hokny/laravel-generator
vonsogt/backup
jengo/queues
jeremykenedy/laravel-observability
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums