在 PHP 中使用 ANSI 转义码
PHP CLI 模式(php -r、php script.php)的 STDOUT 按字节透传 —— echo "\e[31mred\e[0m" 自 PHP 5.4 起可用(该版本将 \e 引入双引号字符串;更早代码请用 "\x1b" 或 chr(27))。Windows Conhost 自 Windows 10 1709 起原生解析 VT,但 PHP 官方建议显式调用 sapi_windows_vt100_support(STDOUT, true)(PHP 7.2+)切换模式,保证跨 host 与 CGI 回退一致。
超出零散 echo 的场景,Symfony Console 是 PHP CLI 框架的事实标准 —— Composer、Laravel artisan、Symfony console、Drupal drush 的底层。其 OutputFormatter 使用类 XML 标签(<info>...</info>、<fg=red;bg=white;options=bold>...</>)在底层编译成 ANSI,输出到非 TTY 时自动去色。Termwind 提供终端版 Tailwind CSS(text-red-500、bg-blue-500、font-bold 之类的 class 名),是 PHPStan、Pest、Laravel Zero 的底层。laravel/prompts 提供现代交互提示,可在 Laravel 之外独立使用。
推荐库
- symfony/console
PHP CLI 框架的事实标准 —— argv 解析、格式化输出、进度条、表格渲染、
QuestionHelper交互提示。输出用类 XML 标签:<info>绿色</info>、<error>红底</error>、<fg=blue;bg=white;options=bold>...</>。stdout 为非 TTY 时格式器自动去色。Composer、Laravelartisan、Symfonyconsole、Drupaldrush都在用。 - nunomaduro/termwind
终端版 Tailwind CSS —— 用 class 名(
text-red-500、bg-blue-500、font-bold、mt-2、px-2、rounded)与类 HTML 标签渲染(render('<div class="text-red-500">oops</div>'))。基于 Symfony Console 的OutputFormatter构建。PHPStan、Pest、Laravel Zero、Lambdish 的底层。 - laravel/prompts
现代交互提示 ——
text、password、confirm、select、multiselect、search、suggest,外观抛光、支持键盘导航。Laravel 10+ 用其替代 Symfony Console 的QuestionHelper,可在任意 PHP 项目独立安装使用。 - league/climate
PHP League 出品的 CLI 工具包 —— 彩色输出、表格、进度条、动画、填充、绘图辅助 —— 提供流式 API(
$climate->red()->bold()->out('oops'))。无需完整命令路由脚手架时,比 Symfony Console 更轻量的替代方案。
常用写法
<?php
echo "\e[1;31merror:\e[0m permission denied\n";
// PHP 5.3 fallback (no \e literal) — use \x1b or chr(27):
echo "\x1b[1;31merror:\x1b[0m permission denied\n";<?php
use Symfony\Component\Console\Output\ConsoleOutput;
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
$out = new ConsoleOutput();
// <info>, <comment>, <question>, <error> ship by default. Define your own:
$out->getFormatter()->setStyle(
'fire',
new OutputFormatterStyle('red', null, ['bold'])
);
$out->writeln('<info>ok:</info> all 142 tests passed');
$out->writeln('<fire>error:</fire> permission denied');
$out->writeln('<fg=blue;bg=white;options=bold>highlighted</> back to normal');
// Auto-strips ANSI when stdout is piped (`php script.php | cat`).<?php
// Stack the canonical signals in priority order:
// --no-color flag → NO_COLOR env → FORCE_COLOR → !isatty → Win VT opt-in
function shouldUseColor(): bool {
if (in_array('--no-color', $GLOBALS['argv'] ?? [], true)) return false;
if (getenv('NO_COLOR') !== false) return false;
if (getenv('FORCE_COLOR') !== false && getenv('FORCE_COLOR') !== '0') return true;
// Windows: PHP 7.2+ exposes a switch for Conhost VT mode. Returns false
// on legacy hosts (Windows 7/8) so the caller correctly falls back.
if (DIRECTORY_SEPARATOR === '\\') {
return function_exists('sapi_windows_vt100_support')
&& sapi_windows_vt100_support(STDOUT, true);
}
// POSIX: STDOUT must be a real TTY.
return function_exists('posix_isatty') && posix_isatty(STDOUT);
}
$useColor = shouldUseColor();
$paint = fn(string $sgr, string $text): string =>
$useColor ? "\e[{$sgr}m{$text}\e[0m" : $text;
echo $paint('1;31', 'error:'), ' permission denied', "\n";<?php
use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\Console\Output\ConsoleOutput;
$out = new ConsoleOutput();
$progress = new ProgressBar($out, 100);
$progress->setFormat(' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%');
$progress->setBarCharacter('<fg=green>=</>');
$progress->setProgressCharacter('<fg=green>></>');
$progress->setEmptyBarCharacter('<fg=gray>-</>');
$progress->start();
for ($i = 0; $i < 100; $i++) {
usleep(20_000);
$progress->advance();
}
$progress->finish();
$out->writeln('');