超越基础的 Symfony 控制台——助手和其他工具
本文由 Wern Ancheta 进行同行评审。 感谢 SitePoint 的所有同行评审员使 SitePoint 内容达到最佳状态!
不可否认的是,控制台命令在开发软件时是多么有用。 不久前我们重新引入了 Symfony 控制台组件。
该组件允许我们创建结构化和可测试的 CLI 命令。 我们创建了一些简单的命令并测试了它们; 但是当我们的命令变得更大更复杂时,我们需要一套不同的工具。
这就是我们今天要看的内容:高级 Symfony 控制台工具。
让我们创建一个命令,我们可以用它来显示其中的一些功能。 大多数基本功能都在 Symfony 控制台文章的重新介绍中展示,所以在继续之前一定要检查它——这是一个快速但有用的阅读!
安装
composer require symfony/console
关于 Composer 的基本信息可以在这里找到,如果您不熟悉设计良好的独立 PHP 环境来开发像 Vagrant 这样的 PHP 应用程序,我们有一本很棒的书,可以在这里购买深入解释它。
创建我们的命令
让我们为最喜欢的命令创建一个命令:Fizzbuzz。
Fizzbuzz 是一个简单的问题,经常用在编程面试中,以证明面试者的编程能力。 Fizzbuzz 的定义通常采用以下形式:
编写一个程序,打印从 1 到 x 的数字。 但是对于三的倍数打印“Fizz”而不是数字,对于五的倍数打印“Buzz”。 对于同时为三和五的倍数的数字,打印“FizzBuzz”。
我们的命令将收到一个参数,该参数将是 Fizzbuzz 的上限。
首先,让我们创建 Fizzbuzz 类。
<?php
declare(strict_types=1);
namespace FizzBuzz;
class Fizzbuzz{
public function isFizz(int $value): bool{
if($value % 3 === 0){
return true;
}
return false;
}
public function isBuzz(int $value): bool{
if($value % 5 === 0){
return true;
}
return false;
}
public function calculateFizzBuzz(int $number): bool{
if($this->isFizz($number) && $this->isBuzz($number)){
echo "FizzBuzz n";
return true;
}
if($this->isFizz($number)){
echo "Fizz n";
return true;
}
if($this->isBuzz($number)){
echo "Buzz n";
return true;
}
echo $number . "n";
return true;
}
public function firstNFizzbuzz(int $maxValue): void{
$startValue = 1;
while($startValue <= $maxValue){
$this->calculateFizzBuzz($startValue);
$startValue++;
}
}
}
非常简单。 这 firstNFizzbuzz()
方法打印 Fizzbuzz 的结果 $maxValue
的数字。 它通过调用 calculateFizzBuzz()
递归的方法。
接下来,让我们编写命令。 创建一个 FizzCommand.php
包含以下内容的文件:
<?php
namespace FizzBuzz;
use SymfonyComponentConsoleCommandCommand;
use SymfonyComponentConsoleInputInputInterface;
use SymfonyComponentConsoleOutputOutputInterface;
use SymfonyComponentConsoleInputInputArgument;
use FizzBuzzFizzbuzz;
class FizzCommand extends Command{
protected function configure(){
$this->setName("FizzBuzz:FizzBuzz")
->setDescription("Runs Fizzbuzz")
->addArgument('Limit', InputArgument::REQUIRED, 'What is the limit you wish for Fizzbuzz?');
}
protected function execute(InputInterface $input, OutputInterface $output){
$fizzy = new FizzBuzz();
$input = $input->getArgument('Limit');
$result = $fizzy->firstNFizzbuzz($input);
}
}
最后我们的 console
文件。
#!/usr/bin/env php
<?php
require_once __DIR__ . '/vendor/autoload.php';
use SymfonyComponentConsoleApplication;
use FizzBuzzFizzCommand;
$app = new Application();
$app->add(new FizzCommand());
$app->run();
在这里我们创建一个新的控制台应用程序并注册我们的 FizzCommand()
进去。 不要忘记使该文件可执行。
我们现在可以通过运行以下命令检查我们的命令是否正确注册 ./console
命令。 我们也可以执行我们的命令 ./console FizzBuzz:Fizzbuzz 25
. 这将计算并打印从 1 到 25 的 Fizzbuzz 结果。
到目前为止,我们还没有做任何新的事情。 但是有几种方法可以改进我们的命令。 首先,命令不是很直观。 我们怎么知道我们必须将限制传递给命令? 为此,Symfony 控制台为我们提供了问题助手。
提问助手
问题助手提供了向用户询问更多信息的功能。 这样我们就可以交互地收集信息以执行我们的命令。
让我们将我们的命令更改为向用户询问限制,而不是通过命令执行提示接收执行限制。 为此,问题助手只有一个方法: ask()
. 该方法接收一个参数 InputInterface
, 一个 OutputInterface
和一个 question
.
让我们改变 FizzCommand.php
文件,所以它看起来像这样:
<?php
namespace FizzBuzz;
use SymfonyComponentConsoleCommandCommand;
use SymfonyComponentConsoleInputInputInterface;
use SymfonyComponentConsoleOutputOutputInterface;
use SymfonyComponentConsoleInputInputArgument;
use SymfonyComponentConsoleQuestionQuestion;
use FizzBuzzFizzbuzz;
class FizzCommand extends Command{
protected function configure(){
$this->setName("FizzBuzz:FizzBuzz")
->setDescription("Runs Fizzbuzz");
}
protected function execute(InputInterface $input, OutputInterface $output){
$fizzy = new FizzBuzz();
$helper = $this->getHelper('question');
$question = new Question('Please select a limit for this execution: ', 25);
$limit = $helper->ask($input, $output, $question);
$result = $fizzy->firstNFizzbuzz($limit);
}
}
我们不再期望就 configure()
方法。 我们实例化一个新的 Question
默认值为 25 并在 ask()
我们前面讲过的方法。
现在我们有一个交互式命令,它在执行 Fizzbuzz 之前要求限制。
问题助手还为我们提供了验证答案的功能。 所以让我们用它来确保限制是一个整数。
protected function execute(InputInterface $input, OutputInterface $output){
$fizzy = new FizzBuzz();
$helper = $this->getHelper('question');
$question = new Question('Please select a limit for this execution: ', 25);
$question->setValidator(function ($answer) {
if (!is_numeric($answer)) {
throw new RuntimeException('The limit should be an integer.');
}
return $answer;
});
$question->setNormalizer(function ($value) {
return $value ? trim($value) : '';
});
$question->setMaxAttempts(2);
$limit = $helper->ask($input, $output, $question);
$result = $fizzy->firstNFizzbuzz($limit);
}
我们不仅通过使用 setValidator()
功能,我们还对输入进行规范化,以防用户插入一些空格,并将允许的最大尝试次数设置为两次。
问题助手提供了更多功能,例如让用户从答案列表中进行选择、多个答案、隐藏用户答案和自动完成。 官方文档对此有更多信息。
表
控制台组件提供的另一个非常有用的功能是显示表格数据的可能性。
要显示表格,我们需要使用 Table
班级; 设置标题和行,最后渲染表格。 这在显示结构化数据时非常有用。 假设我们要创建一个命令来显示某些公制系统的转换。
让我们添加 MetricsCommand.php
到我们的新 php 文件。
<?php
namespace Metric;
use SymfonyComponentConsoleCommandCommand;
use SymfonyComponentConsoleInputInputInterface;
use SymfonyComponentConsoleOutputOutputInterface;
use SymfonyComponentConsoleInputInputArgument;
use SymfonyComponentConsoleHelperTable;
class MetricsCommand extends Command{
protected function configure(){
$this->setName("Metrics")
->setDescription("Inches to centimeters table.");
}
public function execute(InputInterface $input, OutputInterface $output){
$table = new Table($output);
$table
->setHeaders(array('Inches', 'Centimeters'))
->setRows(array(
array('1', '2.54'),
array('5', '12.7'),
array('10', '25.4'),
array('50', '127'),
))
;
$table->render();
}
}
而我们的新 console
文件:
#!/usr/bin/env php
<?php
require_once __DIR__ . '/vendor/autoload.php';
use SymfonyComponentConsoleApplication;
use MetricMetricsCommand;
$app = new Application();
$app->add(new MetricsCommand());
$app->run();
这是一个非常简单的命令:它呈现一个表格,其中包含一些从英寸转换为厘米的值。 如果我们使用 ./console Metrics
,结果将是这样的:
这 Table
class 还为我们的表格提供了不同的分隔符样式。 如果您想了解更多信息,请查看此页面。 .
进度条
虽然问题和表格可能非常有用,但最重要的元素可能是进度条。 进度条为我们提供有关命令执行的反馈,并让我们清楚地了解我们可能需要等待多长时间才能完成操作。
进度条对于长时间运行的命令是必不可少的。 要使用它们,我们需要 ProgressBar
,传递给它一个总单位数(如果我们真的知道我们期望有多少单位)并在命令执行时推进它。
带有进度条的简单命令可能如下所示:
<?php
namespace Progress;
use SymfonyComponentConsoleCommandCommand;
use SymfonyComponentConsoleInputInputInterface;
use SymfonyComponentConsoleOutputOutputInterface;
use SymfonyComponentConsoleInputInputArgument;
use SymfonyComponentConsoleHelperProgressBar;
class ProgressCommand extends Command{
protected function configure(){
$this->setName("Progress")
->setDescription("Check Console componenet progress bar.");
}
public function execute(InputInterface $input, OutputInterface $output)
{
$progress = new ProgressBar($output);
$progress->start();
$i = 0;
while ($i++ < 50) {
usleep(300000);
$progress->advance();
}
$progress->finish();
}
}
以及各自的 console
:
#!/usr/bin/env php
<?php
require_once __DIR__ . '/vendor/autoload.php';
use SymfonyComponentConsoleApplication;
use ProgressProgressCommand;
$app = new Application();
$app->add(new ProgressCommand());
$app->run();
这是一个非常简单的命令。 我们设置栏并循环 sleep()
功能。 最终输出将如下所示:
更多关于进度条的信息可以在官方文档中找到。
自定义我们的进度条
自定义进度条对于在用户等待时提供额外信息很有用。
默认情况下,进度条中显示的信息取决于 OutputInterface
实例。 所以,如果我们想显示不同层次的信息,我们可以使用 setFormat()
方法。
$bar->setFormat('verbose');
内置格式为: normal
, verbose
, very_verbose
和 debug
.
如果我们使用 normal
例如格式,结果将如下所示:
我们也可以设置自己的格式。
进度条是由不同的特定占位符组成的字符串。 我们可以组合这些特定的占位符来创建我们自己的进度条。 可用的占位符是: current
, max
, bar
, percent
, elapsed
, remaining
, estimated
, memory
和 message
. 因此,例如,如果我们想要复制完全相同的默认进度条,我们可以使用以下内容:
$bar->setFormat(' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s% %memory:6s%');
自定义进度条还有很多 – 在这里阅读。
在命令中调用命令
另一个非常有用的功能是能够在命令中运行命令。 例如,我们可能有一个命令依赖于另一个命令才能成功运行,或者我们可能希望按顺序运行一系列命令。
例如,假设我们想要创建一个命令来运行我们的 fizzbuzz 命令。 我们需要在我们的内部创建一个新命令 /src
文件夹和里面 execute()
方法,有以下内容:
protected function execute(InputInterface $input, OutputInterface...