0x00 前言

呼,前阵子在实习所以有点懈怠了博客更新,现在辞职了就整理一下这阵子的收获吧。因为在实习过程中有遇到需要编写命令行应用的场景,用 Shell 脚本的话首先自己不熟(而且觉得 Shell 脚本语法奇葩,很多坑),所以选择了用自己最熟悉的 PHP 来编写脚本。在前期直接用原生的 PHP 写,后面功能越加越多,就有点写崩了的感觉。后面就找到了 Symfony 的 Console 组件进行重构,不得不说这真是个构建命令行工具的利器。所以这篇文章主要介绍的是如何使用 symfony/console 组件构建命令行应用,只是一个简单教程。英文熟练的同学直接看 官方文档 就好。

0x01 初始化项目

上面是一个通过 composer init 初始化项目并添加 symfony/console 的简单视频,执行完后项目目录应该如下所示

1
2
3
4
5
6
7
8
.
├── composer.json
├── composer.lock
├── src
└── vendor
├── autoload.php
├── composer
└── symfony

现在项目已经建好并安装好依赖了。如果你想在一个旧项目中使用 symfony/console 组件,直接执行 composer require symfony/console 安装依赖就好。

0x02 构建 Application

以上述项目为例,在 src 目录下新建一个 Application.php 作为命令行应用的核心文件。

1
2
3
4
5
6
7
8
9
#!/usr/bin/env php
<?php
require __DIR__ . '/../vendor/autoload.php';

use Symfony\Component\Console\Application;

$application = new Application();

$application->run();

以上代码其实已经构建了一个命令行应用,只是什么都命令都没有而已。直接执行它 ./Application.php,会输出如下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Console Tool

Usage:
command [options] [arguments]

Options:
-h, --help Display this help message
-q, --quiet Do not output any message
-V, --version Display this application version
--ansi Force ANSI output
--no-ansi Disable ANSI output
-n, --no-interaction Do not ask any interactive question
-v|vv|vvv, --verbose Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug

Available commands:
help Displays help for a command
list Lists commands

注意:请确保 Application.php 文件有执行权限,否则会无法执行。

看到这个输出是不是觉得很熟悉,因为很多项目在命令行方面都是基于这个组件开发的喔!比如:Laravel 和 composer。

现在我们可以完善一下这个命令行应用的信息,修改后代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
#!/usr/bin/env php
<?php
require __DIR__ . '/../vendor/autoload.php';

use Symfony\Component\Console\Application;

$application = new Application();

$application->setName('测试 Symfony Console 命令行工具'); // 设置应用名称
$application->setVersion('0.0.1'); // 设置应用版本

$application->run();

现在直接执行 ./Application.php 可以看到如下输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
测试 Symfony Console 命令行工具 0.0.1

Usage:
command [options] [arguments]

Options:
-h, --help Display this help message
-q, --quiet Do not output any message
-V, --version Display this application version
--ansi Force ANSI output
--no-ansi Disable ANSI output
-n, --no-interaction Do not ask any interactive question
-v|vv|vvv, --verbose Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug

Available commands:
help Displays help for a command
list Lists commands

可以看见出现了命令行应用的名称和版本号。

0x03 注册命令

现在我们就来试一下如何自定义一个命令行命令吧!在 src 目录下新建个 Commands 目录存放我们的命令。在 Commands 新建个 TestCommand.php 类,其内容如下:

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
<?php
namespace App\Commands;

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

// 一个 Symfony Console 的命令需要继承 Symfony\Component\Console\Command\Command 这个类
class TestCommand extends Command
{
protected static $defaultName = 'app:test'; // 命令的名称

// 命令执行前调用的方法,用于设置命令的帮助信息和命令需要的参数和选项
public function configure()
{
// 设置命令简介,输出命令列表时显示
$this->setDescription('测试命令');
// 设置命令帮助描述,使用 help <命令名称> 时会输出
$this->setHelp('这是一个无关紧要的测试命令,只会输出一条测试字符串');
}

// 命令执行时调用该方法,并且会传入一个输入对象和输出对象。
public function execute(InputInterface $input, OutputInterface $output)
{
$output->writeln('测试');
}
}

然后我们在 Application.php 里加入这个命令,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#!/usr/bin/env php
<?php
// application.php

require __DIR__ . '/../vendor/autoload.php';

use Symfony\Component\Console\Application;

$application = new Application();

$application->setName('测试 Symfony Console 命令行工具');
$application->setVersion('0.0.1');

$application->add(new \App\Commands\TestCommand());

$application->run();

现在我们执行 ./Application.php app:test 可以看到输出了一行 测试

注意:如果出现类不存在之类的错误,请确保 composer.json 里设置了 psr-4 自动加载,将 src 目录映射到 App 命名空间,并执行 composer dumpautoload

0x04 输入参数和选项

很多情况下我们的命令行应用都需要获取一些参数和选项作为条件,symfony/console 也提供了很方便的方法,让我们配置命令行参数和选项。

示例代码如下:

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
<?php
namespace App\Commands;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

class InputCommand extends Command
{
protected static $defaultName = 'app:input';

public function configure()
{
$this->setDescription('测试输入参数和选项');
$this->setHelp('测试输入参数和选项');
// 配置参数 name,为必选
$this->addArgument('name', InputArgument::REQUIRED, '名字');
// 配置参数 age,为可选且默认值为 18
$this->addArgument('age', InputArgument::OPTIONAL, '年龄', 18);
// 配置选项 isHan,缩写为 -x,可选,不需要值
$this->addOption('isHan', 'x', InputOption::VALUE_NONE, '是否汉族');
}

public function execute(InputInterface $input, OutputInterface $output)
{
// 获取 name 参数
$name = $input->getArgument('name');
// 获取 age 参数,如果没填则为默认值
$age = $input->getArgument('age');
// 获取 isHan 选项,对于 InputOption::VALUE_NONE 类型的选项得到的是 bool 值
$han = $input->getOption('isHan');
$output->writeln('姓名:' . $name);
$output->writeln('年龄:' . $age);
$output->writeln('是否汉族:' . ($han ? '是' : '否'));
}
}

Application.php 里加入命令后我们可以测试一下,结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
-> % ./Application.php app:input jack 24 --isHan
姓名:jack
年龄:24
是否汉族:是
---
-> % ./Application.php app:input mike
姓名:mike
年龄:18
是否汉族:否
---
-> % ./Application.php app:input jhon 32
姓名:jhon
年龄:32
是否汉族:否

可以看出使用该组件获取输入参数之类的是很方便的,如果需要更高级的应用可以直接看 官方文档 Input ,或者直接调整到源码看文档注释(Symfony 组件的源码质量真的很高,直接看一些方法的文档注释就能大概知道怎么用了)。

0x05 输出

OutputInterface 这个接口支持 write(直接输出,不带换行) 和 writeln (输出一行)这两种输出方法,其中 writeln 的参数支持数组,意味着输出多行,如下所示:

1
2
3
4
5
6
7
8
9
$output->writeln([
'line 1: 第一行',
'line 2: 第二行',
'line 3: 第三行',
]);
// 输出如下
//line 1: 第一行
//line 2: 第二行
//line 3: 第三行

当然输出时可以指定输出文字的颜色,如下所示:

1
2
3
4
5
6
7
8
9
10
11
// 默认背景,绿色文本
$output->writeln('<info>foo</info>');

// 默认背景,黄色文本
$output->writeln('<comment>foo</comment>');

// 青色背景,黑色文本
$output->writeln('<question>foo</question>');

// 红色背景,白色文本
$output->writeln('<error>foo</error>');

当然也可以自己指定文本颜色和背景颜色甚至是字体样式,如下例:

1
2
3
4
5
6
7
8
9
10
11
// 绿色文本
$output->writeln('<fg=green>foo</>');

// 青色背景,黑色文本
$output->writeln('<fg=black;bg=cyan>foo</>');

// 加粗字体,黄色文本
$output->writeln('<bg=yellow;options=bold>foo</>');

// 加粗字体,下划线
$output->writeln('<options=bold,underscore>foo</>');

在 Symfony Console 4.3 版本有个新特性,允许我们输出可点击的超链接,如下例:

1
$output->writeln('<href=https://symfony.com>Symfony Homepage</>');

注意:这个特性需要终端支持可点击超链接,不然会被当做普通文本正常输出!

0x06 结语

只是一篇简单的入门分享,英文好的同学直接看官方文档即可。有挺多高级点的功能没有讲到,想了解请查阅文档 https://symfony.com/doc/current/components/console.html#learn-more