跳到内容

控制台命令

编辑此页

Symfony 框架通过 bin/console 脚本提供了许多命令 (例如,众所周知的 bin/console cache:clear 命令)。这些命令是使用 Console 组件 创建的。你也可以使用它来创建你自己的命令。

运行命令

每个 Symfony 应用程序都带有一组大量的命令。你可以使用 list 命令来查看应用程序中所有可用的命令

1
2
3
4
5
6
7
8
9
10
11
12
13
$ php bin/console list
...

Available commands:
  about                                      Display information about the current project
  completion                                 Dump the shell completion script
  help                                       Display help for a command
  list                                       List commands
 assets
  assets:install                             Install bundle's web assets under a public directory
 cache
  cache:clear                                Clear the cache
...

注意

list 是默认命令,所以运行 php bin/console 效果相同。

如果你找到了你需要的命令,你可以使用 --help 选项来运行它,以查看该命令的文档

1
$ php bin/console assets:install --help

注意

--help 是 Console 组件的内置全局选项之一,所有命令都可以使用,包括你可以创建的命令。要了解更多关于它们的信息,你可以阅读 本节

APP_ENV & APP_DEBUG

控制台命令在 .env 文件APP_ENV 变量中定义的环境中运行,默认情况下是 dev。它还读取 APP_DEBUG 值来开启或关闭 "debug" 模式 (默认为 1,即开启)。

要在另一个环境或调试模式下运行命令,请编辑 APP_ENVAPP_DEBUG 的值。你也可以在运行命令时定义这些环境变量,例如

1
2
# clears the cache for the prod environment
$ APP_ENV=prod php bin/console cache:clear

控制台补全

如果你正在使用 Bash、Zsh 或 Fish shell,你可以安装 Symfony 的补全脚本,以便在终端中输入命令时获得自动补全。所有命令都支持名称和选项补全,有些甚至可以补全值。

The terminal completes the command name "secrets:remove" and the argument "SOME_OTHER_SECRET".

首先,你必须一次性安装补全脚本。运行 bin/console completion --help 查看你的 shell 的安装说明。

注意

当使用 Bash 时,请确保你已经为你的操作系统安装并设置了 "bash completion" 包 (通常命名为 bash-completion)。

安装并重启你的终端后,你就完成了补全的设置 (默认情况下,按下 Tab 键)。

提示

许多 PHP 工具都是使用 Symfony Console 组件构建的 (例如 Composer、PHPstan 和 Behat)。如果它们使用的是 5.4 或更高版本,你也可以安装它们的补全脚本来启用控制台补全

1
2
$ php vendor/bin/phpstan completion --help
$ composer completion --help

提示

如果你正在使用 Symfony 本地 Web 服务器,建议使用内置的补全脚本,这将确保在运行控制台补全时使用正确的 PHP 版本和配置。运行 symfony completion --help 查看你的 shell 的安装说明。Symfony CLI 将为 consolecomposer 命令提供补全。

创建命令

命令在扩展 Command 的类中定义。例如,你可能想要一个创建用户的命令

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
32
// src/Command/CreateUserCommand.php
namespace App\Command;

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

// the name of the command is what users type after "php bin/console"
#[AsCommand(name: 'app:create-user')]
class CreateUserCommand extends Command
{
    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        // ... put here the code to create the user

        // this method must return an integer number with the "exit status code"
        // of the command. You can also use these constants to make code more readable

        // return this if there was no problem running the command
        // (it's equivalent to returning int(0))
        return Command::SUCCESS;

        // or return this if some error happened during the execution
        // (it's equivalent to returning int(1))
        // return Command::FAILURE;

        // or return this to indicate incorrect command usage; e.g. invalid options
        // or missing arguments (it's equivalent to returning int(2))
        // return Command::INVALID
    }
}

配置命令

你可以选择性地定义描述、帮助信息和 输入选项和参数,通过覆盖 configure() 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// src/Command/CreateUserCommand.php

// ...
class CreateUserCommand extends Command
{
    // ...
    protected function configure(): void
    {
        $this
            // the command description shown when running "php bin/console list"
            ->setDescription('Creates a new user.')
            // the command help shown when running the command with the "--help" option
            ->setHelp('This command allows you to create a user...')
        ;
    }
}

提示

使用 #[AsCommand] 属性来定义描述,而不是使用 setDescription() 方法,可以让你在不实例化其类的情况下获取命令描述。这使得 php bin/console list 命令运行得更快。

如果你想让 list 命令始终快速运行,请为其添加 --short 选项 (php bin/console list --short)。这将避免实例化命令类,但它不会显示任何使用 setDescription() 方法而不是属性来定义命令描述的命令的描述。

configure() 方法在命令构造函数的末尾自动调用。如果你的命令定义了自己的构造函数,请先设置属性,然后调用父构造函数,以使这些属性在 configure() 方法中可用

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
// ...
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;

class CreateUserCommand extends Command
{
    // ...

    public function __construct(bool $requirePassword = false)
    {
        // best practices recommend to call the parent constructor first and
        // then set your own properties. That wouldn't work in this case
        // because configure() needs the properties set in this constructor
        $this->requirePassword = $requirePassword;

        parent::__construct();
    }

    protected function configure(): void
    {
        $this
            // ...
            ->addArgument('password', $this->requirePassword ? InputArgument::REQUIRED : InputArgument::OPTIONAL, 'User password')
        ;
    }
}

注册命令

你可以通过向命令添加 AsCommand 属性来注册该命令

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

use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;

#[AsCommand(
    name: 'app:create-user',
    description: 'Creates a new user.',
    hidden: false,
    aliases: ['app:add-user']
)]
class CreateUserCommand extends Command
{
    // ...
}

如果你不能使用 PHP 属性,请将命令注册为服务,并使用 console.command 标签标记它。如果你正在使用默认的 services.yaml 配置,这已经为你完成了,这要归功于 自动配置

运行命令

配置和注册命令后,你可以在终端中运行它

1
$ php bin/console app:create-user

正如你可能期望的那样,这个命令将什么都不做,因为你还没有编写任何逻辑。在 execute() 方法中添加你自己的逻辑。

控制台输出

execute() 方法可以访问输出流,以便向控制台写入消息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// ...
protected function execute(InputInterface $input, OutputInterface $output): int
{
    // outputs multiple lines to the console (adding "\n" at the end of each line)
    $output->writeln([
        'User Creator',
        '============',
        '',
    ]);

    // the value returned by someMethod() can be an iterator (http://php.ac.cn/iterator)
    // that generates and returns the messages with the 'yield' PHP keyword
    $output->writeln($this->someMethod());

    // outputs a message followed by a "\n"
    $output->writeln('Whoa!');

    // outputs a message without adding a "\n" at the end of the line
    $output->write('You are about to ');
    $output->write('create a user.');

    return Command::SUCCESS;
}

现在,尝试执行该命令

1
2
3
4
5
6
$ php bin/console app:create-user
User Creator
============

Whoa!
You are about to create a user.

输出区域

常规控制台输出可以分为多个独立的区域,称为 "输出区域"。当你需要清除和覆盖输出信息时,创建这些区域中的一个或多个。

区域是使用 ConsoleOutput::section() 方法创建的,该方法返回 ConsoleSectionOutput 的实例

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
32
33
34
35
36
37
38
39
40
41
42
43
44
// ...
use Symfony\Component\Console\Output\ConsoleOutputInterface;

class MyCommand extends Command
{
    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        if (!$output instanceof ConsoleOutputInterface) {
            throw new \LogicException('This command accepts only an instance of "ConsoleOutputInterface".');
        }

        $section1 = $output->section();
        $section2 = $output->section();

        $section1->writeln('Hello');
        $section2->writeln('World!');
        sleep(1);
        // Output displays "Hello\nWorld!\n"

        // overwrite() replaces all the existing section contents with the given content
        $section1->overwrite('Goodbye');
        sleep(1);
        // Output now displays "Goodbye\nWorld!\n"

        // clear() deletes all the section contents...
        $section2->clear();
        sleep(1);
        // Output now displays "Goodbye\n"

        // ...but you can also delete a given number of lines
        // (this example deletes the last two lines of the section)
        $section1->clear(2);
        sleep(1);
        // Output is now completely empty!

        // setting the max height of a section will make new lines replace the old ones
        $section1->setMaxHeight(2);
        $section1->writeln('Line1');
        $section1->writeln('Line2');
        $section1->writeln('Line3');

        return Command::SUCCESS;
    }
}

注意

当在区域中显示信息时,会自动追加一个新行。

输出区域允许你以高级方式操作控制台输出,例如 显示多个独立更新的进度条向已渲染的表格追加行

警告

终端只允许覆盖可见内容,因此在尝试写入/覆盖区域内容时,必须考虑控制台的高度。

控制台输入

使用输入选项或参数将信息传递给命令

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
use Symfony\Component\Console\Input\InputArgument;

// ...
protected function configure(): void
{
    $this
        // configure an argument
        ->addArgument('username', InputArgument::REQUIRED, 'The username of the user.')
        // ...
    ;
}

// ...
public function execute(InputInterface $input, OutputInterface $output): int
{
    $output->writeln([
        'User Creator',
        '============',
        '',
    ]);

    // retrieve the argument value using getArgument()
    $output->writeln('Username: '.$input->getArgument('username'));

    return Command::SUCCESS;
}

现在,你可以将用户名传递给命令

1
2
3
4
5
$ php bin/console app:create-user Wouter
User Creator
============

Username: Wouter

另请参阅

阅读 控制台输入 (参数和选项) 以获取有关控制台选项和参数的更多信息。

从服务容器获取服务

要实际创建一个新用户,该命令必须访问一些服务。由于你的命令已经注册为服务,你可以使用正常的依赖注入。假设你有一个 App\Service\UserManager 服务,你想访问它

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 App\Service\UserManager;
use Symfony\Component\Console\Command\Command;

class CreateUserCommand extends Command
{
    public function __construct(
        private UserManager $userManager,
    ){
        parent::__construct();
    }

    // ...

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        // ...

        $this->userManager->create($input->getArgument('username'));

        $output->writeln('User successfully generated!');

        return Command::SUCCESS;
    }
}

命令生命周期

命令有三个生命周期方法,在运行命令时调用

initialize() (可选)
此方法在 interact()execute() 方法之前执行。其主要目的是初始化命令方法的其余部分中使用的变量。
interact() (可选)
此方法在 initialize() 之后和 execute() 之前执行。其目的是检查是否缺少某些选项/参数,并以交互方式向用户询问这些值。这是你可以询问缺少的必需选项/参数的最后一个位置。此方法在验证输入之前调用。请注意,当命令在没有交互的情况下运行时 (例如,当传递 --no-interaction 全局选项标志时),将不会调用它。
execute() (必需)
此方法在 interact()initialize() 之后执行。它包含你希望命令执行的逻辑,并且必须返回一个整数,该整数将用作命令退出状态

测试命令

Symfony 提供了几个工具来帮助你测试你的命令。最有用的一个是 CommandTester 类。它使用特殊的输入和输出类来简化在没有真实控制台的情况下的测试

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
32
33
34
35
// tests/Command/CreateUserCommandTest.php
namespace App\Tests\Command;

use Symfony\Bundle\FrameworkBundle\Console\Application;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Symfony\Component\Console\Tester\CommandTester;

class CreateUserCommandTest extends KernelTestCase
{
    public function testExecute(): void
    {
        self::bootKernel();
        $application = new Application(self::$kernel);

        $command = $application->find('app:create-user');
        $commandTester = new CommandTester($command);
        $commandTester->execute([
            // pass arguments to the helper
            'username' => 'Wouter',

            // prefix the key with two dashes when passing options,
            // e.g: '--some-option' => 'option_value',
            // use brackets for testing array value,
            // e.g: '--some-option' => ['option_value'],
        ]);

        $commandTester->assertCommandIsSuccessful();

        // the output of the command in the console
        $output = $commandTester->getDisplay();
        $this->assertStringContainsString('Username: Wouter', $output);

        // ...
    }
}

如果你正在使用单命令应用程序,请在其上调用 setAutoExit(false) 以在 CommandTester 中获取命令结果。

提示

你还可以通过使用 ApplicationTester 来测试整个控制台应用程序。

警告

当使用 CommandTester 类测试命令时,不会分派控制台事件。如果你需要测试这些事件,请改用 ApplicationTester

警告

当使用 ApplicationTester 类测试命令时,不要忘记禁用自动退出标志

1
2
3
4
$application = new Application();
$application->setAutoExit(false);

$tester = new ApplicationTester($application);

警告

当测试 InputOption::VALUE_NONE 命令选项时,你必须将 true 传递给它们

1
2
$commandTester = new CommandTester($command);
$commandTester->execute(['--some-option' => true]);

注意

当在独立项目中使用 Console 组件时,使用 Application 并扩展正常的 \PHPUnit\Framework\TestCase

当测试你的命令时,了解你的命令如何对不同的设置 (如终端的宽度和高度,甚至正在使用的颜色模式) 做出反应可能很有用。你可以通过 Terminal 类访问此类信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
use Symfony\Component\Console\Terminal;

$terminal = new Terminal();

// gets the number of lines available
$height = $terminal->getHeight();

// gets the number of columns available
$width = $terminal->getWidth();

// gets the color mode
$colorMode = $terminal->getColorMode();

// changes the color mode
$colorMode = $terminal->setColorMode(AnsiColorMode::Ansi24);

记录命令错误

每当在运行命令时抛出异常时,Symfony 都会为其添加日志消息,包括整个失败的命令。此外,Symfony 注册了一个 事件订阅器 来监听 ConsoleEvents::TERMINATE 事件,并在命令未以 0 退出状态结束时添加日志消息。

使用事件和处理信号

当命令正在运行时,会分派许多事件,其中一个事件允许对信号做出反应,请在 本节中阅读更多内容。

分析命令性能

Symfony 允许分析任何命令 (包括你自己的命令) 的执行情况。首先,确保启用 调试模式性能分析器。然后,在运行命令时添加 --profile 选项

1
$ php bin/console --profile app:my-command

Symfony 现在将收集有关命令执行的数据,这有助于调试错误或检查其他问题。当命令执行完毕后,可以通过性能分析器的网页访问该分析。

提示

如果你在详细模式下运行命令 (添加 -v 选项),Symfony 将在输出中显示一个指向命令分析的可点击链接 (如果你的终端支持链接)。如果你在调试详细程度 (-vvv) 下运行它,你还将看到命令消耗的时间和内存。

警告

当分析来自 Messenger 组件的 messenger:consume 命令时,向命令添加 --no-reset 选项,否则你将不会获得任何分析。此外,考虑使用 --limit 选项仅处理少量消息,以使分析在性能分析器中更易于阅读。

了解更多

控制台组件还包含一组 "助手" - 不同的小型工具,能够帮助你完成不同的任务

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