pmjones / auto-shell
自动解析命令行字符串到命令类。
Requires
- php: ^8.1 | ^8.2 | ^8.3
Requires (Dev)
- pds/composer-script-names: ^1.0
- pds/skeleton: ^1.0
- phpstan/phpstan: ^1.0
- phpunit/phpunit: ^10.0
- pmjones/php-styler: 0.x-dev
This package is auto-updated.
Last update: 2024-08-27 22:39:29 UTC
README
AutoShell 会自动将 CLI 命令名称映射到指定命名空间内的 PHP 命令类,并在此类指定的主方法中反射,以确定参数和选项值。方法参数可以是标量值(int, float, string, bool)或数组。
AutoShell 没有依赖,维护量低。只需将一个类添加到您的源代码中,在可识别的命名空间中,并且具有可识别的主方法名称,就可以自动使其作为命令可用。
将 AutoShell 视为您的 CLI 命令类的“路由器”
-
在 ADR 或 MVC 中,您有前端控制器将 URL 传递给路由器,并获取一个描述要调用哪个 Action/Controller(及其参数)的路由对象。然后前端控制器将使用这些参数调用 Action/Controller。
-
在这里,您有一个 Console 类将
$_SERVER['argv']
传递给 Shell,并获取一个描述要调用哪个 Command 类(及其选项和参数)的 Exec 对象。然后 Console 将使用这些选项和参数调用 Command。
也就是说
Front Controller => Console
Router => Shell
Route => Exec
Action/Controller => Command
入门指南
注意
本文档中的示例遵循 pds/skeleton 标准的目录和文件名。
安装
使用 Composer 安装 AutoShell
composer require pmjones/auto-shell
控制台脚本
您需要一个控制台脚本来运行您的命令。要创建控制台脚本,请在您的项目中打开一个文件,在 bin/console.php
中添加以下代码
<?php use AutoShell\Console; require dirname(__DIR__) . '/vendor/autoload.php'; $console = Console::new( namespace: 'Project\Cli\Command', directory: dirname(__DIR__) . '/src/Cli/Command', help: 'The console for my Project.' . PHP_EOL . PHP_EOL, ); $code = $console($_SERVER['argv']); exit($code);
您需要指定命令类的 namespace
以及那些类文件保存的 directory
。您还可以指定在所有帮助输出顶部显示的 help
文本,但这不是必需的。
现在您可以发出 php bin/console.php
并看到一些输出
The console for my Project.
No commands found.
Namespace: Project\Cli\Command\
Directory: /path/to/project/src/Cli/Command
由于还没有命令,所以可以预期这个输出。
命令类
在 src/Project/Cli/Command/Hello.php
中打开一个文件并添加以下代码
<?php namespace Project\Cli\Command; class Hello { public function __invoke(string $name) : int { echo "Hello {$name}" . PHP_EOL; return 0; } }
这就完成了 -- 命令现在应该可以通过控制台脚本来使用。如果您发出以下 ...
php bin/console.php hello world
... 您应该看到 Hello world
作为输出。
注意
此示例使用
echo
生成输出,但您可以使用任何其他喜欢的输出机制。
添加选项
要启用命令上的选项,创建一个实现了 Options 标记接口的类,使用 #[Option]
属性在构造器提升的属性上。然后,将此 Options 实现添加到主方法参数中,以使这些选项可用于命令逻辑。
首先,在 src/Project/Cli/Command/HelloOptions.php
中打开一个文件并添加以下代码
<?php namespace Project\Cli\Command\HelloOptions; use AutoShell\Option; use AutoShell\Options; class HelloOptions implements Options { public function __construct( #[Option('u,upper')] public readonly ?bool $useUpperCase ) { } }
然后在命令中,为主方法添加一个类型提示的主方法参数,并添加一些选项行为的逻辑
<?php namespace Project\Cli\Command; class Hello { public function __invoke( HelloOptions $options, string $name ) : int { if ($options->useUpperCase) { $name = strtoupper($name); } echo "Hello {$name}" . PHP_EOL; return 0; } }
现在如果您发出以下之一 ...
php bin/console.php hello world -u
php bin/console.php hello world --upper
... 您将看到 Hello WORLD
作为输出。
提供帮助
要为您的命令添加帮助,请使用 #[Help]
属性。
编辑命令,在类及其主方法参数上添加 #[Help]
属性
<?php namespace Project\Cli\Command; use AutoShell\Help; #[Help("Says hello to a _name_ of your choice.")] class Hello { public function __invoke( HelloOptions $options, #[Help("The _name_ to say hello to.")] string $name ) : int { if ($options->useUpperCase) { $name = strtoupper($name); } echo "Hello {$name}" . PHP_EOL; return 0; } }
同样,编辑每个 #[Option]
属性,添加一个 help
参数
<?php namespace Project\Cli\Command\HelloOptions; use AutoShell\Option; use AutoShell\Options; class HelloOptions implements Options { public function __construct( #[Option('u,upper', help: "Output the _name_ in upper case.")] public readonly ?bool $useUpperCase ) { } }
现在,当您发出 php bin/console.php
或 php bin/console.php help
时,您应该看到您的命令列在命令列表中
The console for my Project.
hello
Says hello to a name of your choice.
同样,当您发出 php bin/console.php help hello
时,您应该看到您的命令的手册页
The console for my Project.
NAME
hello
SYNOPSIS
hello [options] [--] name ...
ARGUMENTS
name
The name to say hello to.
OPTIONS
-u
--upper
Output the name in upper case.
高级主题
命令命名
命令类文件假定按照PSR-4标准命名;进一步
-
破折号分隔的单词转换为驼峰式
-
冒号表示命名空间分隔符
例如,给定基本命名空间为 Project\Cli\Command
,命令名称 create-article
映射到类 Project\Cli\Command\CreateArticle
。
同样,命令名称 schema:dump
映射到类 Project\Cli\Command\Schema\Dump
。
Shell 会解析命令名称以找到正确的类,然后在该类中反射“main”方法(通常是 __invoke()
),以查找可用的选项和参数,并将它们解析出来。(Shell 忽略接口、特性、抽象类和 Options 实现。)
如果您想使用后缀命名命令类,则在创建 Console 对象时指定该后缀
$console = Console::new( namespace: 'Project\Cli\Command', directory: dirname(__DIR__) . '/src/Cli/Command', suffix: 'Command' );
命令方法
默认情况下,AutoShell 将 __invoke()
反射为命令类上的主方法。您可以在创建 Console 时更改该主方法。例如,要将 exec()
用作命令上的主方法
$console = Console::new( namespace: 'Project\Cli\Command', directory: dirname(__DIR__) . '/src/Cli/Command', method: 'exec' );
最后,您的主方法签名应指示 Console 退出代码为 int
。返回 0
表示成功,而任何其他整数值表示失败或错误。请参阅 http://www.unix.com/man-page/freebsd/3/sysexits/ 了解常见的退出代码。
命令工厂
默认情况下,Console 将仅使用 new
创建命令类。这对于入门来说很好,但您可能需要为命令类提供某种形式的依赖注入。您可以通过在 Console 上设置命令工厂来实现这一点。
要设置一个根据类名创建命令对象的工厂,例如基于 psr/container 的一个工厂,将一个 $factory
可调用对象传递给 Console
/** @var Psr\Container\ContainerInterface $container */ $console = Console::new( namespace: 'Project\Cli\Command', directory: dirname(__DIR__) . '/src/Cli/Command', factory: fn (string $class) => $container->get($class), );
Console 不会使用注入的工厂来创建其自己的帮助类;它将自行创建这些类。
参数类型
AutoShell 识别主方法参数类型提示为 int
、float
、string
、bool
、mixed
和 array
,并将自动将来自命令行调用的值收集并转换为相应的类型。
对于 bool
,AutoShell 将这些参数值不区分大小写地转换为 true
: 1, t, true, y, yes
。同样,它将这些参数值不区分大小写地转换为 false
: 0, f, false, n, no
。
对于 array
,AutoShell 将使用 str_getcsv()
对参数值进行操作以生成数组。例如,对于参数值为 a,b,c
的数组类型提示,将接收 ['a', 'b', 'c']
。
最后,AutoShell 也会尊重尾部可变参数。
选项定义
您可以通过向实现 Options 标记接口的类中提升的属性添加 #[Option]
属性来为您的命令定义长选项和短选项。
属性名称可以是您喜欢的任何名称,但 必须为可空。(AutoShell 通过将其设置为 null
来指示未在命令行中传递选项。)
作为良好的实践,该属性应定义为 readonly
,并且不应有默认值,但这不是强制性的。
每个 #[Option]
的第一个参数是选项的短名称和长名称的逗号分隔列表,这是必需的
<?php namespace Project\Cli\Command; use AutoShell\Option; use AutoShell\Options; class FooOptions implements Options { public function __construct( #[Option('b,bar')] public readonly ?bool $barval, ) { } }
每个 #[Option]
属性有几个可选的命名参数
-
mode
:(字符串)必须是Option::VALUE_REJECTED
、VALUE_REQUIRED
或VALUE_OPTIONAL
之一。默认为VALUE_REJECTED
。- 如果
VALUE_REJECTED
,则选项 不允许 指定值;当指定选项时,其值始终为默认值(见下文)。 - 如果
VALUE_REQUIRED
,则选项 必须 指定值; - 如果
VALUE_OPTIONAL
,则选项 可以 指定值;如果选项未指定值,其值为默认值(见下文)。
- 如果
-
default
:(混合类型)指定选项未指定值时的值。默认值为true
。 -
multiple
:(布尔值)当true
时,选项可以多次指定,并且值将以数组的形式传递。(特别说明:如果mode
为VALUE_REJECTED
且属性类型为int
,则值将是一个整数,表示选项指定的次数。)默认值为false
。 -
help
:(字符串)关于此选项的简短帮助文本,用于手册页面。
值将转换为 #[Option]
的属性类型。
在您的命令中,您可以通过主方法上的 Options 参数来引用选项
<?php namespace Project\Cli\Command; class Foo { public function __invoke(FooOptions $options) : int { if ($options->barval) { // $barval is true } return 0; } }
组合选项
有时您可能希望所有命令都使用一组共同的 Options,同时还有一组针对单个命令的 Options。为了支持这一点,AutoShell 允许在主方法上有多个 Options 参数
<?php namespace Project\Cli\Command; class Foo { public function __invoke( CommonOptions $commonOptions, FooOptions $fooOptions ) : int { if ($commonOptions->verbose) { // increased verbosity } if ($fooOptions->bar) { // do whatever 'bar' means } return 0; } }
对此的唯一限制是,在参数中 较晚 指定的 Options 可能没有在任何一个 较早 的 Options 参数中定义的选项名称。
根据上述示例,这意味着您不能在 CommonOptions 和 FooOptions 中同时定义(例如)-f
;您必须只在一个中定义它。如果您在多个中定义它,AutoShell 将引发一个 OptionAlreadyDefined 异常。
扩展帮助
您可以在命令级别的 Help 中添加额外的、长文本作为第二个参数。支持非常轻量级的标记,如 *粗体*
和 _下划线_
。
<?php namespace Project\Cli\Command; use AutoShell\Help; #[Help( 'This command does something.', <<<HELP *DESCRIPTION* This is a longer description of the command. *EXAMPLES* Look for examples _elsewhere_. HELP )] class Foo { // ... }
控制台输入/输出
默认情况下,Console 将帮助输出写入 STDOUT
,并将调用时间错误消息写入 STDERR
(例如,当它无法解析命令行输入时)。
要更改 Console 写入输出的位置,请传递一个用于 $stdout
和/或 $stderr
参数的可调用对象
/** @var Psr\Container\ContainerInterface $container */ $logger = $container->get(LoggerInterface::class); $console = Console::new( namespace: 'Project\Cli\Command', directory: dirname(__DIR__) . '/src/Cli/Command', stdout: fn (string $output) => $logger->info($output), stderr: fn (string $output) => $logger->error($output), );
请注意,这些可调用对象仅由 Console 本身使用——而且即使如此,也仅用于帮助和错误输出。
命令输入/输出
AutoShell 不需要或提供任何命令 I/O 机制。这意味着您的命令类可以使用您喜欢的任何 I/O 系统;这完全由您自己控制。
在入门时,您可能只想使用 echo
、printf()
等类似的功能。然而,这可能会变得麻烦,尤其是在您想要开始自动化测试时。您需要缓冲所有命令输出,捕获它,然后读取它以断言输出正确性。
作为替代方案,您可以通过传递一个写入 STDOUT 和 STDERR 资源句柄的 psr/log
实现,例如 pmjones/stdlog。然后在测试中,您可以使用 php://memory
资源句柄实例化实现,并从内存中读取命令输出。
最后,您可能希望注入一个更强大的独立 CLI 输入/输出系统。据称 league/climate 很好,但我没有使用过它。