超越基础的 Symfony 控制台——助手和其他工具

本文由 Wern Ancheta 进行同行评审。 感谢 SitePoint 的所有同行评审员使 SitePoint 内容达到最佳状态!

不可否认的是,控制台命令在开发软件时是多么有用。 不久前我们重新引入了 Symfony 控制台组件。

该组件允许我们创建结构化和可测试的 CLI 命令。 我们创建了一些简单的命令并测试了它们; 但是当我们的命令变得更大更复杂时,我们需要一套不同的工具。

这就是我们今天要看的内容:高级 Symfony 控制台工具。

让我们创建一个命令,我们可以用它来显示其中的一些功能。 大多数基本功能都在 Symfony 控制台文章的重新介绍中展示,所以在继续之前一定要检查它——这是一个快速但有用的阅读!

安装

composer require symfony/console

关于 Composer 的基本信息可以在这里找到,如果您不熟悉设计良好的独立 PHP 环境来开发像 Vagrant 这样的 PHP 应用程序,我们有一本很棒的书,可以在这里购买深入解释它。

创建我们的命令

让我们为最喜欢的命令创建一个命令:Fizzbuzz。

Fizzbuzz 是一个简单的问题,经常用在编程面试中,以证明面试者的编程能力。 Fizzbuzz 的定义通常采用以下形式:

编写一个程序,打印从 1 到 x 的数字。 但是对于三的倍数打印“Fizz”而不是数字,对于五的倍数打印“Buzz”。 对于同时为三和五的倍数的数字,打印“FizzBu​​zz”。

我们的命令将收到一个参数,该参数将是 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_verbosedebug.

如果我们使用 normal 例如格式,结果将如下所示:

我们也可以设置自己的格式。

进度条是由不同的特定占位符组成的字符串。 我们可以组合这些特定的占位符来创建我们自己的进度条。 可用的占位符是: current, max, bar, percent, elapsed, remaining, estimated, memorymessage. 因此,例如,如果我们想要复制完全相同的默认进度条,我们可以使用以下内容:

$bar->setFormat(' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s% %memory:6s%');

自定义进度条还有很多 – 在这里阅读。

在命令中调用命令

另一个非常有用的功能是能够在命令中运行命令。 例如,我们可能有一个命令依赖于另一个命令才能成功运行,或者我们可能希望按顺序运行一系列命令。

例如,假设我们想要创建一个命令来运行我们的 fizzbuzz 命令。 我们需要在我们的内部创建一个新命令 /src 文件夹和里面 execute() 方法,有以下内容:

protected function execute(InputInterface $input, OutputInterface...

阅读更多

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注