vanilla / garden-cli
一个功能全面但极其简单的命令行解析器,适用于您下一个PHP CLI脚本。停止与getopt()战斗。
Requires (Dev)
- ergebnis/composer-normalize: ^2.8
- phpdocumentor/reflection-docblock: ^5.3
- phpunit/phpunit: ^8
- vanilla/garden-container: ^3.0
- vanilla/standards: ^1.3
- vimeo/psalm: ^4.0
Suggests
- ext-pdo: Required for the DbUtils class.
- phpdocumentor/reflection-docblock: Required for the CliApplication functionality.
- vanilla/garden-container: Required for the CliApplication functionality.
- dev-master
- v4.0
- v3.1.2
- v3.1.1
- v3.1
- v3.0.1
- v3.0
- v2.2
- v2.1
- v2.0
- v1.6.3
- v1.6.2
- v1.6.1
- v1.6.0
- v1.5.0
- v1.4.0
- v1.3.0
- v1.2.1
- v1.2.0
- v1.1.0
- v1.0.0
- v1.0.0-beta
- dev-feature/php-8.1-support
- dev-fix/command-subclass
- dev-fix/no-type-setter
- dev-add/dispatch-internal
- dev-feature/cli-app-args
- dev-feature/setters-optional
- dev-refactor/cli-application
- dev-add/muchos
- dev-feature/array-type
- dev-feature/updates
- dev-feature/multi-log
- dev-feature/logger
- dev-feature/travis-php72
- dev-feature/php-5.6
- dev-test/cla
This package is auto-updated.
Last update: 2024-08-25 00:05:58 UTC
README
介绍
Garden CLI是一个PHP命令行界面库,旨在提供一个干净简单的API,提供全面的功能。
为什么使用Garden CLI?
PHP的getopt()
提供的功能很少,并且容易出错,如果命令行选项中有一个打字错误,可能会导致整个命令调用失败。Garden CLI解决了这个问题,并提供了额外的功能。
- 您的命令将自动支持
--help
以打印命令的帮助信息。 - 支持单个命令或多条命令。(例如,git pull,git push等)
- 命令选项将自动解析和验证,并自动打印错误信息。
- 简单优雅的语法,即使是最基础的命令行脚本也只需少许努力即可实现健壮的解析。
安装
Garden CLI需要PHP 8.1或更高版本
Garden CLI符合PSR-4规范,并可以使用composer安装。只需将vanilla/garden-cli
添加到您的composer.json文件中。
"require": { "vanilla/garden-cli": "~4.0" }
定义CLI
Cli
类提供了一个流畅的接口来定义命令、选项和参数。
基本示例
以下是一个基本示例,说明如何使用Garden CLI解析其选项的命令行脚本。假设您正在编写一个名为dbdump.php
的脚本,用于从数据库中导出一些数据。
<?php // All of the command line classes are in the Garden\Cli namespace. use Garden\Cli\Cli; // Require composer's autoloader. require_once 'vendor/autoload.php'; // Define the cli options. $cli = new Cli(); $cli->description('Dump some information from your database.') ->opt('host:h', 'Connect to host.', true) ->opt('port:P', 'Port number to use.', false, 'integer') ->opt('user:u', 'User for login if not current user.', true) ->opt('password:p', 'Password to use when connecting to server.') ->opt('database:d', 'The name of the database to dump.', true); // Parse and return cli args. $args = $cli->parse($argv, true);
此示例返回一个Args
对象,或退出以显示帮助或错误消息。以下是关于示例的一些注意事项。
- 您可以通过将
false
作为parse()
的第二个参数传递来抛出异常而不是退出。 opt
方法有以下参数:name
、description
、required
和type
。大多数参数都有合理的默认值。- 如果您想为选项指定简写代码,请使用冒号将其与
name
参数分开。 - 如果您为选项指定了简写代码,这将仅作为参数名称在
$argv
中的别名。您在解析后始终通过其完整名称访问选项。
选项类型
opt
方法有一个$type
参数,您可以使用它来指定选项的类型。有效类型是integer
、string
和boolean
,默认为string
。
您还可以在类型名称后添加[]
来指定数组。要在命令行上提供数组,您需要多次指定选项,如下所示
command --header="line1" --header="line2"
显示帮助
如果您使用--help
选项调用基本示例,则会看到以下帮助信息打印出来
usage: dbdump.php [<options>] Dump some information from your database. OPTIONS --database, -d The name of the database to dump. --help, -? Display this help. --host, -h Connect to host. --password, -p Password to use when connecting to server. --port, -P Port number to use. --user, -u User for login if not current user.
所有选项都打印在一个紧凑的表格中,且必需选项以粗体显示。表格会自动扩展以适应较长的选项名称,并在提供额外的长描述时自动换行。
显示错误
假设您只使用-P foo
调用基本示例,您会看到以下错误消息
The value of --port (-P) is not a valid integer. Missing required option: database Missing required option: user
使用解析后的选项
一旦您使用Cli->parse($argv)
成功解析了$argv
,您就可以使用返回的Args
对象上的各种方法。
$args = $cli->parse($argv); $host = $args->getOpt('host', '127.0.0.1'); // get host with default 127.0.0.1 $user = $args->getOpt('user'); // get user $database = $args['database']; // use the args like an array too $port = $args->getOpt('port', 123); // get port with default 123
多条命令示例
假设您正在编写一个类似于git的命令行工具,名为nit.php
,该工具可以从远程仓库推送和拉取信息。
// Define a cli with commands. $cli = Cli::create() // Define the first command: push. ->command('push') ->description('Push data to a remote server.') ->opt('force:f', 'Force an overwrite.', false, 'boolean') ->opt('set-upstream:u', 'Add a reference to the upstream repo.', false, 'boolean') // Define the second command: pull. ->command('pull') ->description('Pull data from a remote server.') ->opt('commit', 'Perform the merge and commit the result.', false, 'boolean') // Set some global options. ->command('*') ->opt('verbose:v', 'Output verbose information.', false, 'integer') ->arg('repo', 'The repository to sync with.', true); $args = $cli->parse($argv);
像基本示例一样,parse()
在成功解析后会返回一个Args
对象。以下是一些关于此示例的注意事项。
- 如果想要在定义命令架构时拥有100%流畅的接口,则提供了
Cli::create()
方法。 - 调用
command()
方法来定义一个新的命令。 - 如果调用
command('*')
,则可以定义适用于所有命令的全局选项。 - 如果
opt()
的类型是integer
,则可以统计一个选项被提供的次数。在此示例中,这允许您通过添加多个-v
来指定多个级别的详细程度。 arg()
方法允许您定义位于命令行选项之后的参数。下面将详细介绍。
列出命令
调用没有选项或仅包含--help
选项的脚本将显示命令列表。以下是上述多个命令示例的输出。
usage: nit.php <command> [<options>] [<args>] COMMANDS push Push data to a remote server. pull Pull data from a remote server.
Args和Opts
Args
类区分了args和opts。在该类上提供了访问opts和args的方法。
- Opts通过
--name
全名或-s
短代码传递。它们是有名称的,并且可以有类型。 - Args作为选项之后传递,作为由空格分隔的字符串。
- 在从命令行调用脚本时,如果您有歧义,可以使用
--
来分隔opts和args。
CliApplication类
Cli
类的基本功能对于定义和记录opts和args非常有效。但是,您仍然需要将解析后的命令行args连接到自己的代码。如果您想减少这种样板代码,可以使用CliApplication
类。
注意:为了使用CliApplication
功能,您将需要引入一些额外的依赖。有关更多信息,请参阅composer.json中建议的包。
定义CliApplication的子类
要使用CliApplication
,通常您会继承它并重写configureContainer()
和configureCli()
方法以定义您应用中的命令。
class App extends Garden\Cli\Application\CliApplication { protected function configureCli(): void { parent::configureCli(); // Add methods with addMethod(). $this->addMethod('SomeClassName', 'someMethod'); $this->addMethod('SomeClassName', 'someOtherMethod', [CliApplication::OPT_SETTERS => false]); $this->addMethod('SomeOtherClassName', 'someMethod', [CliApplication::OPT_COMMAND => 'command-name']); // Add command classes with addCommandClass(). $this->addCommandClass('ExampleCommand', 'run'); // Add ad-hoc closures with addCallable(). $this->addCallable('foo', function (int $count) { }); // Wire up dependencies with addConstructor() or addFactory(). $this->addFactory(\PDO::class, [\Garden\Cli\Utility\DbUtils::class, 'createMySQL']); } protected function configureContainer(Container $container): void { parent::configureContainer($container); // Configure the container here. } }
此示例连接了三个方法。
使用addMethod()
方法
您可以通过使用addMethod()
将类方法连接到命令行。这将执行以下操作:
- 它将创建一个从方法名派生的命令。使用
OPT_COMMAND
选项覆盖命令名称。 - 它将可选地创建用于对象设置器的opts。对象设置器是以下一个参数的以单词
set
开头的函数。您可以使用OPT_SETTERS
选项取消设置器连接。 - 它将创建用于方法参数的opts。如果方法有类类型提示类型,则它们不会连接到opts,而是由容器满足。
- 它将使用方法文档块来添加命令和opts的描述。请确保您使用PHPDoc语法。
您可以使用静态或实例方法调用addMethod()
。如果您传递静态方法,则它将只连接静态设置器。实例方法将连接静态和实例方法。
使用addCommandClass()
方法
addCommandClass()
方法与addMethod()
非常相似,但有以下不同之处:
- 命令名称将从类名推断。您可以使用
OPT_COMMAND_REGEX
来删除前缀或后缀。默认情况下,正则表达式将删除以"Job"或"Command"结尾的类的后缀。 - 它将从类描述中推断命令描述。
- 默认情况下,从设置器创建opts。
使用addCallable()
方法
您可以通过使用addCallable()
将一个临时的闭包连接到命令行。这与addMethod()
非常相似,但它只会反映可调用的参数。
尽管在行内闭包中添加文档块不是常见的做法,但你仍然可以这样做,并且它将被用来记录命令。如果你不这样做,但至少想提供一个描述,请使用OPT_DESCRIPTION
选项来提供。
使用addConstructor()
和addFactory()
方法
你可以通过将构造函数参数或工厂方法连接到opts来添加依赖。最常见的情况是指定数据库的连接参数或API客户端的访问令牌。
如果类的构造函数参数来自命令行有意义,请使用addConstructor()
。
如果你想要清理参数的名称或以某种方式执行一些额外的属性,请使用addFactory()
。
如果构造函数或工厂具有类类型提示,则无需担心。它们将通过容器自动注入。然后,你可以通过容器目录进行配置,甚至可以通过调用addConstructor()
或addFactory()
来将它们连接到opts。
class App extends Garden\Cli\Application\CliApplication { protected function configureCli(): void { parent::configureCli(); // This will make the database connection get created by the DbUtils::createMySQL() method with command line opts for the same. $this->addFactory(\PDO::class, [\Garden\Cli\Utility\DbUtils::class, 'createMySQL']); $this->getContainer()->setShared(true); // This will wire up the constructor parameters for the the StreamLogger to the command line and set is as the logger for the app. $this->addConstructor(\Garden\Cli\StreamLogger::class); $this->getContainer()->setShared(true); $this->getContainer()->rule(\Psr\Log\LoggerInterface::class)->setAliasOf(\Garden\Cli\StreamLogger::class); } }
使用addCall()
方法
你可以使用addCall()
方法将一个类方法连接起来。用于setter注入。在类实例化时应用此调用。
class App extends Garden\Cli\Application\CliApplication { protected function configureCli(): void { parent::configureCli(); // Wire up your github client's API key to the command line. $this->addCall(GithubClient::class, 'setAPIKey', [\Garden\Cli\Application\CliApplication::OPT_PREFIX => 'git-']); } }
运行您的应用程序
要使用您的应用程序,只需调用main()
方法。
$app = new App(); $app->main($argv);
主方法执行以下操作
- 解析
$argv
参数以确定命令。 - 如果命令映射到实例方法,则从容器中获取实例。
- 应用从opts中设置setter。
- 通过容器调用该方法,满足任何未指定为opts的参数。
从Garden CLI应用程序迁移到CliApplication
如果您想将旧的Garden CLI应用程序迁移到CliApplication,则需要执行以下操作
-
用
CliApplication
替换您对Cli
类的使用。CliApplication
是主Cli
类的子类。因此,如果您有一个使用Cli
类的应用程序,则只需将您的实例替换为CliApplication
并使用您的旧代码。 -
重写
CliApplication::dispatchInternal()
方法,并将您的switch语句或相关内容移到那里。确保在您的代码之后调用parent::dispatchInternal()
,通常作为switch的默认值。 -
将您的
$cli->parse($argv)
调用替换为$cli->main($argv)
调用。这将解析参数并将调度到您的dispatchInternal()
方法。 -
现在您可以开始用
CliApplication
特定方法的调用替换一些样板代码。如果您愿意,可以保留旧样板代码不变,只是为新代码使用CliApplication
辅助程序。
日志记录
许多CLI应用程序需要某种形式的日志记录。Garden CLI可以满足这一需求。
使用TaskLogger格式化输出
TaskLogger
是一个PSR-3日志装饰器,它可以帮助您以优雅、紧凑的样式将基于任务的信息输出到控制台。它适用于像安装脚本、耗时脚本或放入cron作业中的脚本这样的场景。
日志记录任务
当使用TaskLogger
时,您需要从消息和任务的角度思考。消息是要输出给用户的一个单独的日志项。任务有一个开始和一个结束,并且可以无限嵌套。使用各种PSR-3方法输出消息,而使用begin()
和end()
输出任务。以下是您可以用于记录任务的全部方法。
任务嵌套和持续时间
您可以通过在调用 end*
方法之前调用 begin*
方法来无限嵌套任务。每次嵌套任务时,它将输出其消息,并缩进一个级别。任务还会计算其持续时间,并在调用 end
后输出。
抑制消息
默认情况下,TaskLogger
只会输出 LogLevel::INFO
级别或更高级别的消息。您可以使用 setMinLevel
方法来更改此设置。如果在一个被抑制的级别开始任务,但子消息在或高于最小级别,则开始任务的消息将被回溯输出。这允许您看到哪个任务触发了日志消息。
示例
$log = new TaskLogger(); $log->info('This is a message.'); $log->error('This is an error.'); // outputs in red $log->beginInfo('Begin a task'); // code task code goes here... $log->end('done.'); $log->beginDebug('Make an API call'); $log->endHttpStatus(200); // treated as error or success depending on code $log->begin(LogLevel::NOTICE, 'Multi-step task'); $log->info('Step 1'); $log->info('Step 2'); $log->beginDebug('Step 3'); $log->debug('Step 3.1'); // steps will be hidden because they are level 3 $log->debug('Step 3.2'); $log->end('done.'); $log->end('done.');
StreamLogger
如果您创建并使用 TaskLogger
对象,它将默认输出到控制台。在内部,它使用 StreamLogger
对象来处理将任务格式化到输出流(在这种情况下是 stdout)的操作。如果您想更细粒度地控制日志记录,可以替换或修改 StreamLogger
。以下是一些选项。
示例
以下示例创建了一个 StreamLogger
对象,并在将其传递给 TaskLogger
构造函数之前调整了一些设置。
$fmt = new StreamLogger(STDOUT); $fmt->setLineFormat('{level}: {time} {message}'); $fmt->setLevelFormat('strtoupper'); $fmt->setTimeFormat(function ($ts) { return number_format(time() - $ts).' seconds ago'; }); $log = new TaskLogger($fmt);
实现自己的日志记录器
您可以将任何符合 PSR-3 的日志记录器提供给 TaskLogger
,并将输出发送到它。为了使用一些特殊任务功能,您需要检查 log
方法的 $contenxt
参数。这里是一些您可能会收到的字段。