控制台输入(参数和选项)
命令最有趣的部分是您可以使用的参数和选项。这些参数和选项允许您将动态信息从终端传递到命令。
使用命令参数
参数是命令名称本身后面的字符串(用空格分隔)。它们是有序的,可以是可选的或必需的。例如,要向命令添加一个可选的 last_name
参数,并将 name
参数设为必需
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
// ...
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
class GreetCommand extends Command
{
// ...
protected function configure(): void
{
$this
// ...
->addArgument('name', InputArgument::REQUIRED, 'Who do you want to greet?')
->addArgument('last_name', InputArgument::OPTIONAL, 'Your last name?')
;
}
}
您现在可以在命令中访问 last_name
参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
// ...
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
{
$text = 'Hi '.$input->getArgument('name');
$lastName = $input->getArgument('last_name');
if ($lastName) {
$text .= ' '.$lastName;
}
$output->writeln($text.'!');
return Command::SUCCESS;
}
}
现在可以通过以下任一方式使用该命令
1 2 3 4 5
$ php bin/console app:greet Fabien
Hi Fabien!
$ php bin/console app:greet Fabien Potencier
Hi Fabien Potencier!
也可以让一个参数接受一个值列表(想象一下您想要问候所有朋友)。只有最后一个参数可以是列表
1 2 3 4 5 6 7 8
$this
// ...
->addArgument(
'names',
InputArgument::IS_ARRAY,
'Who do you want to greet (separate multiple names with a space)?'
)
;
要使用此功能,请指定任意数量的名称
1
$ php bin/console app:greet Fabien Ryan Bernhard
您可以将 names
参数作为数组访问
1 2 3 4
$names = $input->getArgument('names');
if (count($names) > 0) {
$text .= ' '.implode(', ', $names);
}
您可以使用三个参数变体
InputArgument::REQUIRED
- 参数是强制性的。如果未提供参数,则命令不会运行;
InputArgument::OPTIONAL
- 参数是可选的,因此可以省略。这是参数的默认行为;
InputArgument::IS_ARRAY
- 参数可以包含任意数量的值。因此,它必须在参数列表的末尾使用。
您可以像这样将 IS_ARRAY
与 REQUIRED
或 OPTIONAL
结合使用
1 2 3 4 5 6 7 8
$this
// ...
->addArgument(
'names',
InputArgument::IS_ARRAY | InputArgument::REQUIRED,
'Who do you want to greet (separate multiple names with a space)?'
)
;
使用命令选项
与参数不同,选项不是有序的(意味着您可以按任何顺序指定它们),并且用两个破折号指定(例如 --yell
)。选项始终是可选的,并且可以设置为接受一个值(例如 --dir=src
)或作为没有值的布尔标志(例如 --yell
)。
例如,向命令添加一个新选项,该选项可用于指定应连续打印消息的次数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
// ...
use Symfony\Component\Console\Input\InputOption;
$this
// ...
->addOption(
// this is the name that users must type to pass this option (e.g. --iterations=5)
'iterations',
// this is the optional shortcut of the option name, which usually is just a letter
// (e.g. `i`, so users pass it as `-i`); use it for commonly used options
// or options with long names
null,
// this is the type of option (e.g. requires a value, can be passed more than once, etc.)
InputOption::VALUE_REQUIRED,
// the option description displayed when showing the command help
'How many times should the message be printed?',
// the default value of the option (for those which allow to pass values)
1
)
;
接下来,在命令中使用它来多次打印消息
1 2 3
for ($i = 0; $i < $input->getOption('iterations'); $i++) {
$output->writeln($text);
}
现在,当您运行命令时,您可以选择指定 --iterations
标志
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
# no --iterations provided, the default (1) is used
$ php bin/console app:greet Fabien
Hi Fabien!
$ php bin/console app:greet Fabien --iterations=5
Hi Fabien!
Hi Fabien!
Hi Fabien!
Hi Fabien!
Hi Fabien!
# the order of options isn't important
$ php bin/console app:greet Fabien --iterations=5 --yell
$ php bin/console app:greet Fabien --yell --iterations=5
$ php bin/console app:greet --yell --iterations=5 Fabien
提示
您还可以声明一个单字母快捷方式,您可以使用单个破折号调用它,例如 -i
1 2 3 4 5 6 7 8 9 10
$this
// ...
->addOption(
'iterations',
'i',
InputOption::VALUE_REQUIRED,
'How many times should the message be printed?',
1
)
;
请注意,为了符合 docopt 标准,长选项可以在空格或 =
符号后指定它们的值(例如 --iterations 5
或 --iterations=5
),但短选项只能使用空格或完全不分隔(例如 -i 5
或 -i5
)。
警告
虽然可以使用空格将选项与其值分隔开,但是如果选项出现在命令名称之前,则使用此形式会导致歧义。例如,php bin/console --iterations 5 app:greet Fabien
是有歧义的;Symfony 会将 5
解释为命令名称。为避免这种情况,请始终将选项放在命令名称之后,或避免使用空格分隔选项名称及其值。
您可以使用五个选项变体
InputOption::VALUE_IS_ARRAY
- 此选项接受多个值(例如
--dir=/foo --dir=/bar
); InputOption::VALUE_NONE
- 不接受此选项的输入(例如
--yell
)。从返回的值是一个布尔值(如果未提供选项,则为false
)。这是选项的默认行为; InputOption::VALUE_REQUIRED
- 此值是必需的(例如
--iterations=5
或-i5
),选项本身仍然是可选的; InputOption::VALUE_OPTIONAL
- 此选项可能有值,也可能没有值(例如
--yell
或--yell=loud
)。 InputOption::VALUE_NEGATABLE
- 接受标志(例如
--yell
)或其否定形式(例如--no-yell
)。
您需要像这样将 VALUE_IS_ARRAY
与 VALUE_REQUIRED
或 VALUE_OPTIONAL
结合使用
1 2 3 4 5 6 7 8 9 10
$this
// ...
->addOption(
'colors',
null,
InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
'Which colors do you like?',
['blue', 'red']
)
;
带有可选参数的选项
没有什么禁止您创建一个带有选项的命令,该选项可以选择性地接受一个值,但这有点棘手。考虑以下示例
1 2 3 4 5 6 7 8 9 10 11 12
// ...
use Symfony\Component\Console\Input\InputOption;
$this
// ...
->addOption(
'yell',
null,
InputOption::VALUE_OPTIONAL,
'Should I yell while greeting?'
)
;
此选项可以通过 3 种方式使用:greet --yell
、greet --yell=louder
和 greet
。但是,很难区分传递没有值的选项(greet --yell
)和不传递选项(greet
)。
要解决此问题,您必须将选项的默认值设置为 false
1 2 3 4 5 6 7 8 9 10 11 12 13
// ...
use Symfony\Component\Console\Input\InputOption;
$this
// ...
->addOption(
'yell',
null,
InputOption::VALUE_OPTIONAL,
'Should I yell while greeting?',
false // this is the new default value, instead of null
)
;
现在可以区分不传递选项和不传递任何选项值了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
$optionValue = $input->getOption('yell');
if (false === $optionValue) {
// in this case, the option was not passed when running the command
$yell = false;
$yellLouder = false;
} elseif (null === $optionValue) {
// in this case, the option was passed when running the command
// but no value was given to it
$yell = true;
$yellLouder = false;
} else {
// in this case, the option was passed when running the command and
// some specific value was given to it
$yell = true;
if ('louder' === $optionValue) {
$yellLouder = true;
} else {
$yellLouder = false;
}
}
上面的代码可以简化如下,因为 false !== null
1 2 3
$optionValue = $input->getOption('yell');
$yell = ($optionValue !== false);
$yellLouder = ($optionValue === 'louder');
获取原始命令输入
Symfony 提供了一个 getRawTokens() 方法来获取传递给命令的原始输入。如果您想自己解析输入,或者当您需要将输入传递给另一个命令而无需担心参数或选项的数量时,这非常有用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
// ...
use Symfony\Component\Process\Process;
protected function execute(InputInterface $input, OutputInterface $output): int
{
// if this command was run as:
// php bin/console app:my-command foo --bar --baz=3 --qux=value1 --qux=value2
$tokens = $input->getRawTokens();
// $tokens = ['app:my-command', 'foo', '--bar', '--baz=3', '--qux=value1', '--qux=value2'];
// pass true as argument to not include the original command name
$tokens = $input->getRawTokens(true);
// $tokens = ['foo', '--bar', '--baz=3', '--qux=value1', '--qux=value2'];
// pass the raw input to any other command (from Symfony or the operating system)
$process = new Process(['app:other-command', ...$input->getRawTokens(true)]);
$process->setTty(true);
$process->mustRun();
// ...
}
7.1
getRawTokens() 方法在 Symfony 7.1 中引入。
添加参数/选项值补全
如果控制台补全已安装,命令和选项名称将由 shell 自动补全。但是,您也可以在命令中为输入实现值补全。例如,您可能希望在 greet 命令的 name
参数中补全数据库中的所有用户名。
为了实现这一点,请使用 addArgument()
/addOption
的第 5 个参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
// ...
use Symfony\Component\Console\Completion\CompletionInput;
use Symfony\Component\Console\Completion\CompletionSuggestions;
class GreetCommand extends Command
{
// ...
protected function configure(): void
{
$this
->addArgument(
'names',
InputArgument::IS_ARRAY,
'Who do you want to greet (separate multiple names with a space)?',
null,
function (CompletionInput $input): array {
// the value the user already typed, e.g. when typing "app:greet Fa" before
// pressing Tab, this will contain "Fa"
$currentValue = $input->getCompletionValue();
// get the list of username names from somewhere (e.g. the database)
// you may use $currentValue to filter down the names
$availableUsernames = ...;
// then suggested the usernames as values
return $availableUsernames;
}
)
;
}
}
这就是您所需要的全部!假设用户 "Fabien" 和 "Fabrice" 存在,在键入 app:greet Fa
后按 Tab 键将为您提供这些名称作为建议。
提示
shell 脚本能够处理大量的建议,并将根据用户的现有输入自动过滤建议的值。您不必在命令中实现任何过滤器逻辑。
如果 CompletionInput::getCompletionValue()
有助于提高性能(例如,通过减少从数据库中获取的行数),您可以使用它来获取当前输入。
测试补全脚本
Console 组件带有一个特殊的 CommandCompletionTester 类,以帮助您单元测试补全逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
// ...
use Symfony\Component\Console\Application;
class GreetCommandTest extends TestCase
{
public function testComplete(): void
{
$application = new Application();
$application->add(new GreetCommand());
// create a new tester with the greet command
$tester = new CommandCompletionTester($application->get('app:greet'));
// complete the input without any existing input (the empty string represents
// the position of the cursor)
$suggestions = $tester->complete(['']);
$this->assertSame(['Fabien', 'Fabrice', 'Wouter'], $suggestions);
// If you filter the values inside your own code (not recommended, unless you
// need to improve performance of e.g. a database query), you can test this
// by passing the user input
$suggestions = $tester->complete(['Fa']);
$this->assertSame(['Fabien', 'Fabrice'], $suggestions);
}
}
命令全局选项
Console 组件向所有命令添加了一些预定义的选项
--verbose
:设置详细级别(例如,1
默认值,2
和3
,或者您可以使用相应的快捷方式-v
、-vv
和-vvv
)--silent
:禁用所有输出和交互,包括错误--quiet
:禁用输出和交互,但错误仍然显示--no-interaction
:禁用交互--version
:输出控制台应用程序的版本号--help
:显示命令帮助--ansi|--no-ansi
:是否强制或禁用输出着色
7.2
--silent
选项在 Symfony 7.2 中引入。
当使用 FrameworkBundle
时,还预定义了两个选项
--env
:设置 Kernel 配置环境(默认为APP_ENV
)--no-debug
:禁用 Kernel 调试(默认为APP_DEBUG
)
因此,您的自定义命令也可以开箱即用地使用它们。