跳到内容

如何设置控制台命令的样式

编辑此页

创建控制台命令时,最枯燥的任务之一是处理命令输入和输出的样式。显示标题和表格或向用户提问涉及大量重复代码。

例如,考虑用于显示以下命令标题的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// src/Command/GreetCommand.php
namespace App\Command;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

class GreetCommand extends Command
{
    // ...

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $output->writeln([
            '<info>Lorem Ipsum Dolor Sit Amet</>',
            '<info>==========================</>',
            '',
        ]);

        // ...
    }
}

显示一个简单的标题需要三行代码,即更改字体颜色、为内容添加下划线并在标题后留出一个额外的空行。对于设计良好的命令来说,处理样式是必需的,但这会不必要地使代码复杂化。

为了减少这些样板代码,Symfony 命令可以选择使用 Symfony 样式指南。这些样式实现为一组辅助方法,允许创建语义化命令并忘记它们的样式。

基本用法

在您的命令中,实例化 SymfonyStyle 类,并将 $input$output 变量作为其参数传递。然后,您可以开始使用它的任何辅助方法,例如 title(),它显示命令的标题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// src/Command/GreetCommand.php
namespace App\Command;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;

class GreetCommand extends Command
{
    // ...

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $io = new SymfonyStyle($input, $output);
        $io->title('Lorem Ipsum Dolor Sit Amet');

        // ...
    }
}

辅助方法

SymfonyStyle 类定义了一些辅助方法,涵盖了控制台命令执行的最常见交互。

标题方法

title()

将给定的字符串显示为命令标题。此方法旨在在给定命令中仅使用一次,但没有什么可以阻止您重复使用它

1
$io->title('Lorem ipsum dolor sit amet');
section()

将给定的字符串显示为某个命令部分的标题。这仅在希望更好地区分其内容的复杂命令中才需要

1
2
3
4
5
6
7
$io->section('Adding a User');

// ...

$io->section('Generating the Password');

// ...

内容方法

text()

将给定的字符串或字符串数组显示为常规文本。这对于呈现帮助消息和运行命令的用户的说明很有用

1
2
3
4
5
6
7
8
9
10
11
// use simple strings for short messages
$io->text('Lorem ipsum dolor sit amet');

// ...

// consider using arrays when displaying long messages
$io->text([
    'Lorem ipsum dolor sit amet',
    'Consectetur adipiscing elit',
    'Aenean sit amet arcu vitae sem faucibus porta',
]);
listing()

显示作为数组传递的元素的无序列表

1
2
3
4
5
$io->listing([
    'Element #1 Lorem ipsum dolor sit amet',
    'Element #2 Lorem ipsum dolor sit amet',
    'Element #3 Lorem ipsum dolor sit amet',
]);
table()

将给定的标题和行数组显示为紧凑的表格

1
2
3
4
5
6
7
8
$io->table(
    ['Header 1', 'Header 2'],
    [
        ['Cell 1-1', 'Cell 1-2'],
        ['Cell 2-1', 'Cell 2-2'],
        ['Cell 3-1', 'Cell 3-2'],
    ]
);
horizontalTable()

将给定的标题和行数组显示为紧凑的水平表格

1
2
3
4
5
6
7
8
$io->horizontalTable(
    ['Header 1', 'Header 2'],
    [
        ['Cell 1-1', 'Cell 1-2'],
        ['Cell 2-1', 'Cell 2-2'],
        ['Cell 3-1', 'Cell 3-2'],
    ]
);
definitionList()

将给定的 key => value 对显示为紧凑的元素列表

1
2
3
4
5
6
7
8
9
$io->definitionList(
    'This is a title',
    ['foo1' => 'bar1'],
    ['foo2' => 'bar2'],
    ['foo3' => 'bar3'],
    new TableSeparator(),
    'This is another title',
    ['foo4' => 'bar4']
);
createTable()
创建 Table 的实例,其样式根据 Symfony 样式指南设置,允许您使用诸如动态追加行等功能。
newLine()

在命令输出中显示一个空行。虽然它看起来可能很有用,但在大多数情况下您根本不需要它。原因是每个辅助方法都已经添加了自己的空行,因此您不必关心垂直间距

1
2
3
4
5
// outputs a single blank line
$io->newLine();

// outputs three consecutive blank lines
$io->newLine(3);

告诫方法

note()

将给定的字符串或字符串数组显示为突出显示的告诫。谨慎使用此辅助方法,以避免使命令的输出混乱

1
2
3
4
5
6
7
8
9
10
11
// use simple strings for short notes
$io->note('Lorem ipsum dolor sit amet');

// ...

// consider using arrays when displaying long notes
$io->note([
    'Lorem ipsum dolor sit amet',
    'Consectetur adipiscing elit',
    'Aenean sit amet arcu vitae sem faucibus porta',
]);
caution()

note() 辅助方法类似,但内容更加突出显示。结果内容类似于错误消息,因此除非绝对必要,否则应避免使用此辅助方法

1
2
3
4
5
6
7
8
9
10
11
// use simple strings for short caution message
$io->caution('Lorem ipsum dolor sit amet');

// ...

// consider using arrays when displaying long caution messages
$io->caution([
    'Lorem ipsum dolor sit amet',
    'Consectetur adipiscing elit',
    'Aenean sit amet arcu vitae sem faucibus porta',
]);

进度条方法

progressStart()

显示一个进度条,其步数等于传递给该方法的参数(如果进度条的长度未知,则不要传递任何值)

1
2
3
4
5
// displays a progress bar of unknown length
$io->progressStart();

// displays a 100-step length progress bar
$io->progressStart(100);
progressAdvance()

使进度条前进给定的步数(如果未传递任何参数,则前进 1 步)

1
2
3
4
5
// advances the progress bar 1 step
$io->progressAdvance();

// advances the progress bar 10 steps
$io->progressAdvance(10);
progressFinish()

完成进度条(当其长度已知时,填充所有剩余步骤)

1
$io->progressFinish();
progressIterate()

如果您的进度条循环遍历可迭代的集合,请使用 progressIterate() 辅助方法

1
2
3
4
5
$iterable = [1, 2];

foreach ($io->progressIterate($iterable) as $value) {
    // ... do some work
}
createProgressBar()
创建 ProgressBar 的实例,其样式根据 Symfony 样式指南设置。

用户输入方法

ask()

要求用户提供一些值

1
$io->ask('What is your name?');

您可以将默认值作为第二个参数传递,以便用户可以按 <Enter> 键来选择该值

1
$io->ask('Where are you from?', 'United States');

如果您需要验证给定的值,请将回调验证器作为第三个参数传递

1
2
3
4
5
6
7
$io->ask('Number of workers to start', '1', function (string $number): int {
    if (!is_numeric($number)) {
        throw new \RuntimeException('You must type a number.');
    }

    return (int) $number;
});
askHidden()

它与 ask() 方法非常相似,但用户的输入将被隐藏,并且它不能定义默认值。在询问敏感信息时使用它

1
$io->askHidden('What is your password?');

如果您需要验证给定的值,请将回调验证器作为第二个参数传递

1
2
3
4
5
6
7
$io->askHidden('What is your password?', function (string $password): string {
    if (empty($password)) {
        throw new \RuntimeException('Password cannot be empty.');
    }

    return $password;
});
confirm()

向用户询问一个“是/否”问题,并且只返回 truefalse

1
$io->confirm('Restart the web server?');

您可以将默认值作为第二个参数传递,以便用户可以按 <Enter> 键来选择该值

1
$io->confirm('Restart the web server?', true);
choice()

提出一个问题,其答案被限制在给定的有效答案列表中

1
$io->choice('Select the queue to analyze', ['queue1', 'queue2', 'queue3']);

您可以将默认值作为第三个参数传递,以便用户可以按 <Enter> 键来选择该值

1
$io->choice('Select the queue to analyze', ['queue1', 'queue2', 'queue3'], 'queue1');

最后,您可以允许用户选择多个选项。为此,用户必须用逗号分隔每个选项(例如,键入 1, 2 将选择选项 1 和 2)

1
$io->choice('Select the queue to analyze', ['queue1', 'queue2', 'queue3'], multiSelect: true);

结果方法

注意

如果您打印任何 URL,它都不会被断开/切断,它将是可点击的 - 如果终端提供此功能。如果“格式良好的输出”更重要,您可以关闭它

1
$io->getOutputWrapper()->setAllowCutUrls(true);
success()

将给定的字符串或字符串数组显示为突出显示为成功消息的消息(带有绿色背景和 [OK] 标签)。它旨在用于显示执行给定命令的最终结果一次,但您可以在命令执行期间重复使用它

1
2
3
4
5
6
7
8
9
10
// use simple strings for short success messages
$io->success('Lorem ipsum dolor sit amet');

// ...

// consider using arrays when displaying long success messages
$io->success([
    'Lorem ipsum dolor sit amet',
    'Consectetur adipiscing elit',
]);
info()

它类似于 success() 方法(给定的字符串或字符串数组以绿色背景显示),但没有前缀 [OK] 标签。它旨在用于显示执行给定命令的最终结果一次,而不将结果显示为成功或失败

1
2
3
4
5
6
7
8
9
10
// use simple strings for short info messages
$io->info('Lorem ipsum dolor sit amet');

// ...

// consider using arrays when displaying long info messages
$io->info([
    'Lorem ipsum dolor sit amet',
    'Consectetur adipiscing elit',
]);
warning()

将给定的字符串或字符串数组显示为突出显示为警告消息的消息(带有红色背景和 [WARNING] 标签)。它旨在用于显示执行给定命令的最终结果一次,但您可以在命令执行期间重复使用它

1
2
3
4
5
6
7
8
9
10
// use simple strings for short warning messages
$io->warning('Lorem ipsum dolor sit amet');

// ...

// consider using arrays when displaying long warning messages
$io->warning([
    'Lorem ipsum dolor sit amet',
    'Consectetur adipiscing elit',
]);
error()

将给定的字符串或字符串数组显示为突出显示为错误消息的消息(带有红色背景和 [ERROR] 标签)。它旨在用于显示执行给定命令的最终结果一次,但您可以在命令执行期间重复使用它

1
2
3
4
5
6
7
8
9
10
// use simple strings for short error messages
$io->error('Lorem ipsum dolor sit amet');

// ...

// consider using arrays when displaying long error messages
$io->error([
    'Lorem ipsum dolor sit amet',
    'Consectetur adipiscing elit',
]);

配置默认样式

默认情况下,Symfony 样式会包装所有内容,以避免文本行过长。唯一的例外是 URL,无论它们有多长,都不会被包装。这样做是为了在支持它们的终端中启用可点击的 URL。

如果您希望包装所有内容,包括 URL,请使用此方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// src/Command/GreetCommand.php
namespace App\Command;

// ...
use Symfony\Component\Console\Style\SymfonyStyle;

class GreetCommand extends Command
{
    // ...

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $io = new SymfonyStyle($input, $output);
        $io->getOutputWrapper()->setAllowCutUrls(true);

        // ...
    }
}

定义您自己的样式

如果您不喜欢使用 Symfony 样式的命令的设计,您可以定义自己的一组控制台样式。创建一个实现 StyleInterface 的类

1
2
3
4
5
6
7
8
namespace App\Console;

use Symfony\Component\Console\Style\StyleInterface;

class CustomStyle implements StyleInterface
{
    // ...implement the methods of the interface
}

然后,在您的命令中实例化这个自定义类,而不是默认的 SymfonyStyle。感谢 StyleInterface,您无需更改命令的代码即可更改其外观

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// src/Command/GreetCommand.php
namespace App\Console;

use App\Console\CustomStyle;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

class GreetCommand extends Command
{
    // ...

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        // Before
        $io = new SymfonyStyle($input, $output);

        // After
        $io = new CustomStyle($input, $output);

        // ...
    }
}

写入错误输出

如果您重用命令的输出作为其他命令的输入,或将其转储到文件中以供以后重用,您可能希望排除进度条、注释和其他不提供实际价值的输出。

命令可以在两个不同的流中输出信息:stdout(标准输出)是应该输出实际内容的流,而 stderr(标准错误)是应该输出错误和调试消息的流。

SymfonyStyle 类提供了一个名为 getErrorStyle() 的便捷方法,用于在两个流之间切换。此方法返回一个新的 SymfonyStyle 实例,该实例使用错误输出

1
2
3
4
5
6
7
$io = new SymfonyStyle($input, $output);

// Write to the standard output
$io->write('Reusable information');

// Write to the error output
$io->getErrorStyle()->warning('Debugging information or errors');

注意

如果您使用不是 ConsoleOutputInterface 实例的 OutputInterface 对象创建 SymfonyStyle 实例,则 getErrorStyle() 方法将不起作用,并且返回的对象仍将写入标准输出而不是错误输出。

本作品,包括代码示例,根据 Creative Commons BY-SA 3.0 许可协议获得许可。
目录
    版本