如何样式化一个命令

3.4 版本
维护中的版本

创建console命令时,一个最乏味的任务就是去处理命令的input和output的样式。显示标题和表格,或是让用户回答问题,总是会牵扯到大量的代码重复。

思考以下用于显示title的命令行例程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// src/AppBundle/Command/GreetCommand.php
namespace AppBundle\Command;
 
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
 
class GreetCommand extends ContainerAwareCommand
{
    // ...
 
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $output->writeln(array(
            '<info>Lorem Ipsum Dolor Sit Amet</>',
            '<info>==========================</>',
            '',
        ));
 
        // ...
    }
}

显示一个简单的标题,需要三行代码,实现了改变字体颜色,对内容加下划线,以及在标题下方留一个空行。设计得当的命令行是需要应对样式的,但却令代码变得不必要的复杂。

为了减少那些样板一样的代码,Symfony命令提供了可选的 Symfony Style Guide。这些样式是通过一组helper method而实现的,助手方法允许创建 语义化 命令而毋须记住其样式。

基本用法 

在你的命令中,对 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/AppBundle/Command/GreetCommand.php
namespace AppBundle\Command;
 
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
 
class GreetCommand extends ContainerAwareCommand
{
    // ...
 
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $io = new SymfonyStyle($input, $output);
        $io->title('Lorem Ipsum Dolor Sit Amet');
 
        // ...
    }
}

helper方法 

SymfonyStyle 类定义了一些助手方法,覆盖了console命令行所需之常见互动。

标题方法 

title()

它把给定的字符串作为命令的标题显示。本方法在一个命令中注定只能使用一次,但并不妨碍你重复地使用它:

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

它把给定的字符串作为命令区段(command 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
12
13
// use simple strings for short messages
// 使用简单字符串作为短消息
$io->text('Lorem ipsum dolor sit amet');
 
// ...
 
// consider using arrays when displaying long messages
// 当显示长信息时考虑使用数组
$io->text(array(
    'Lorem ipsum dolor sit amet',
    'Consectetur adipiscing elit',
    'Aenean sit amet arcu vitae sem faucibus porta',
));
listing()

它把作为数组传入命令的元素,显示成无序清单:

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

它把给定的包含“headers和rows”的数组显示为紧凑表格:

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

它在命令的output中显示空行。虽然看上去有用,但多数时候你根本不需要它。原因是每个helper都已经添加了自己的空行了,所以你不必特意关照垂直间距:

1
2
3
4
5
// outputs a single blank line / 输出单个空行
$io->newLine();
 
// outputs three consecutive blank lines / 输出三个连续的空行
$io->newLine(3);

告诫方法 

note()

它把给定的字符串或字符串数组作为一个高亮的劝告显示出来。It displays the given string or array of strings as a highlighted admonition. 慎用此助手以避免将命令行搞乱:

1
2
3
4
5
6
7
8
9
10
11
12
// use simple strings for short notes / 使用简单字符串以简短提醒
$io->note('Lorem ipsum dolor sit amet');
 
// ...
 
// consider using arrays when displaying long notes
// 当显示较长提示时,考虑使用数组
$io->note(array(
    '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
12
13
// use simple strings for short caution message
// 使用简单字符串以简短警告
$io->caution('Lorem ipsum dolor sit amet');
 
// ...
 
// consider using arrays when displaying long caution messages
// 当显示较长的警告信息时,考虑使用数组
$io->caution(array(
    'Lorem ipsum dolor sit amet',
    'Consectetur adipiscing elit',
    'Aenean sit amet arcu vitae sem faucibus porta',
));

进度条方法 

progressStart()

它显示一个带有的总步数的进度条 ,步数等同于传到方法中的参数(当不确定进度条的长度时,不要传入任何值):

1
2
3
4
5
6
7
// displays a progress bar of unknown length
// 显示一个未知长度的进度条
$io->progressStart();
 
// displays a 100-step length progress bar
// 显示一个100步的进度条
$io->progressStart(100);
progressAdvance()

它令进度条推进给定的步数 (或 1 步,如果没有参数传入的话):

1
2
3
4
5
// advances the progress bar 1 step / 推进进度条1个step
$io->progressAdvance();
 
// advances the progress bar 10 steps / 推进进度条10步
$io->progressAdvance(10);
progressFinish()

完成进度条的推进 (当进度条长度未知时,它完成全部剩余步数):

1
$io->progressFinish();

用户输入方法 

ask()

它要求用户提供一些值:

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

你可以把默认值作为第二个参数传入,以便用户可以简单敲击回车来选中该值:

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

若你需要验证给定的值,把一个callback valicator作为第三个参数传入:

1
2
3
4
5
6
7
$io->ask('Number of workers to start', 1, function ($number) {
    if (!is_integer($number)) {
        throw new \RuntimeException('You must type an integer.');
    }
 
    return $number;
});
askHidden()

它与 ask() 方法非常相似,但用户的输入将被隐藏,且无法定义一个默认值。使用它来问询敏感信息:

1
2
3
4
5
6
7
8
9
10
$io->askHidden('What is your password?');
 
// validates the given answer
$io->askHidden('What is your password?', function ($password) {
    if (empty($password)) {
        throw new \RuntimeException('Password cannot be empty.');
    }
 
    return $password;
});
confirm()

它向用户询问的是一个 Yes/No 的问题,仅能返回 truefalse:

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

你可以把默认值作为第二个参数传入,以便用户可以简单敲击回车来选中该值:

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

它所提问的问题,其答案被限定到一个给定的“有效答案列表”中:

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

你可以把默认值作为第二个参数传入,以便用户可以简单敲击回车来选中该值:

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

结果方法 

success()

它把给定的字符串或字符串数组作为一个成功提示来高亮显示(带有绿色背景和 [OK] 标签)。它只能使用一次,用于显示所执行的命令之最终结果,但你可以在命令执行过程中重复地使用它:

1
2
3
4
5
6
7
8
9
10
11
12
// use simple strings for short success messages
// 对简短的成功提示使用简单字符串
$io->success('Lorem ipsum dolor sit amet');
 
// ...
 
// consider using arrays when displaying long success messages
// 当成功提示较长时,考虑使用数组
$io->success(array(
    'Lorem ipsum dolor sit amet',
    'Consectetur adipiscing elit',
));
warning()

它把给定的字符串或字符串数组作为一个警告信息来高亮显示 (带有红色背景和 [WARNING] 标签))。它只能使用一次,用于显示所执行的命令之最终结果,但你可以在命令执行过程中重复地使用它:

1
2
3
4
5
6
7
8
9
10
11
12
// use simple strings for short warning messages
// 对简短的警告信息使用简单字符串
$io->warning('Lorem ipsum dolor sit amet');
 
// ...
 
// consider using arrays when displaying long warning messages
// 当警告信息较长时,考虑使用数组
$io->warning(array(
    'Lorem ipsum dolor sit amet',
    'Consectetur adipiscing elit',
));
error()

它把给定的字符串或字符串数组作为一个错误信息来高亮显示 (带有红色背景和 [ERROR] 标签)。它只能使用一次,用于显示所执行的命令之最终结果,但你可以在命令执行过程中重复地使用它:

1
2
3
4
5
6
7
8
9
10
11
12
// use simple strings for short error messages
// 对简短的错误信息使用简单字符串
$io->error('Lorem ipsum dolor sit amet');
 
// ...
 
// consider using arrays when displaying long error messages
// 当错误信息较长时,考虑使用数组
$io->error(array(
    'Lorem ipsum dolor sit amet',
    'Consectetur adipiscing elit',
));

定义你自己的样式 

如果你不喜欢Symfony风格的命令行设计,你可以定义自己的控制台风格组合。只要创建一个类去实现 StyleInterface 接口:

1
2
3
4
5
6
7
8
namespace AppBundle\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
namespace AppBundle\Console;
 
use AppBundle\Console\CustomStyle;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
 
class GreetCommand extends ContainerAwareCommand
{
    // ...
 
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        // Before / 之前
        // $io = new SymfonyStyle($input, $output);
 
        // After / 之后
        $io = new CustomStyle($input, $output);
 
        // ...
    }
}

本文,包括例程代码在内,采用的是 Creative Commons BY-SA 3.0 创作共用授权。

登录symfonychina 发表评论或留下问题(我们会尽量回复)