stubbles / console
Stubbles 包用于构建命令行应用程序。
Requires
- php: ^7.0
- stubbles/input: ^7.0
- stubbles/ioc: ^8.0.1
- stubbles/reflect: ^8.0
- stubbles/streams: ^8.1
- stubbles/values: ^8.1
Requires (Dev)
- bovigo/assert: ^2.1
- bovigo/callmap: ^3.2
- mikey179/vfsstream: ^1.6
- phpunit/phpunit: ^5.5
README
支持命令行应用程序。
构建状态
安装
stubbles/console 以 Composer 包的形式分发。要将它作为您包的依赖项安装,请使用以下命令
composer require "stubbles/console": "^7.0"
需求
stubbles/console 至少需要 PHP 7.0。
控制台应用程序类
命令行应用程序的主要点是其控制台应用程序类。它们必须扩展 stubbles\console\ConsoleApp
,并有两个目的
- 提供绑定模块的列表,用于 Stubbles IoC 机制,告诉它如何连接整个应用程序的对象图。
- 获取注入的主逻辑入口,并在其
run()
方法中运行它。根据逻辑的结果,它应该返回适当的退出代码,这意味着 0 表示成功执行,而任何其他退出代码表示发生错误。
有关 IoC 部分的详细信息,请参阅有关 stubbles/ioc 中的应用程序 的文档。一般来说,理解 stubbles/ioc 中的 Inversion of Control 功能将有助于设计您命令行应用程序的进一步类。
有关控制台应用程序类可能的样子示例,请参阅 ConsoleAppCreator,这是下面解释的 createConsoleApp
脚本背后的控制台应用程序类。
退出代码
控制台应用程序类的 run()
方法应返回适当的退出代码。如果运行成功,则应为 0,如果发生错误,则应为非零退出代码。
建议使用介于 21 和 255 之间的退出代码来表示错误。退出代码 1 到 20 由 Stubbles Console 预留,不应使用,而上限 255 是某些操作系统的限制。
预留的退出代码
- 当应用程序的命令行选项或参数之一包含错误时返回。
- 在控制台应用程序类运行期间发生未捕获的异常时返回。
如何创建命令行应用程序
为了简化控制台应用程序的创建,stubbles/console 提供了一个辅助脚本,可以创建控制台应用程序类的骨架、适合调用此控制台应用程序的命令行脚本以及此控制台应用程序类的单元测试骨架。
当您的项目根目录中安装了 stubbles/console 时,只需在命令行中输入
vendor/bin/createConsoleApp
首先,它将询问您要创建的控制台应用程序类的完全限定名称。将其输入到提示符中,然后按回车键确认。不需要转义命名空间分隔符。其次,它将询问您要创建的命令行脚本名称。将其名称输入到提示符中,然后按回车键确认。
一旦脚本完成,您将在应用程序中找到三个新文件
- 一个在 src/main/php 中的类,其类名为您输入的名称,并扩展了
stubbles\console\ConsoleApp
类。 - 一个在 bin 文件夹中的脚本,其名称与您输入的名称相同。
- 为应用程序类编写的单元测试在 src/test/php 中,其类名为您输入的名称。
此时,应用程序类、脚本和单元测试都已经完全可用 - 可以运行它们,但当然什么也不做。
从这个点开始,您可以开始扩展生成的类和单元测试,以实现您想要的功能。
常见问题解答
如果输入的类已经存在会发生什么?
创建脚本将检查在项目内部是否已经存在具有给定名称的类。这包括所有可以通过自动加载功能加载的类。如果存在,将跳过应用类的创建。
如果要创建的脚本已经存在会发生什么?
将跳过脚本的创建。
如果 src/test/php 中已经存在具有此类名的单元测试会发生什么?
将跳过单元测试的创建。
我能否使用创建控制台应用脚本来为已存在的应用生成脚本或单元测试?
是的,这是可能的。只需输入现有应用类的名称。由于类已经存在,创建将被跳过,但如果它们尚未存在,则仍将创建脚本和单元测试。
提供的绑定模块
在您的控制台应用类的 __bindings()
方法中编译绑定模块列表时,您可以利用stubbles/console提供的绑定模块。您的控制台应用类应该扩展的 stubbles\console\ConsoleApp
类提供了静态辅助方法来创建这些绑定模块
argumentParser()
创建一个绑定模块,该模块解析通过命令行传递的任何参数,并将它们添加为绑定以供注入。有关解析命令行参数的更多详细信息,请参见下文。
解析命令行参数
大多数时候,命令行应用需要通过脚本的调用者传递参数。当参数绑定模块被添加到绑定模块列表中(见上文)时,stubbles/console将使这些参数可用于注入到应用类的类中。
无特殊解析
默认情况下,所有通过命令行给出的参数都以数组形式和单个值形式提供。假设命令行调用为 ./exampleScript foo bar baz
,则以下值将可用于注入
@Named('argv')
:输入值的整个数组:array('foo', 'bar', 'baz')
。@Named('argv.0')
:不是被调用脚本名称的第一个值,在这种情况下为 foo。@Named('argv.1')
:不是被调用脚本名称的第二个值,在这种情况下为 bar。@Named('argv.2')
:不是被调用脚本名称的第三个值,在这种情况下为 baz。
请求未传递的值,例如此示例中的 @Named('argv.3')
,将导致 stubbles\ioc\BindingException
。
特殊解析
在某些情况下,您可能需要进行更复杂的参数解析。这也是可能的
self::argumentParser() ->withOptions($options) ->withLongOptions(array $options)
使用此方法,我们可以这样解析参数
./exampleScript -n foo -d bar --other baz -f --verbose
为了正确解析此示例,参数绑定模块必须配置如下
self::argumentParser() ->withOptions('fn:d:') ->withLongOptions(array('other:', 'verbose'))
有关选项语法的更多详细信息,请参阅PHP手册中的 getopt()。
如果以这种方式解析参数,它们将以以下名称可用于注入
@Named('argv')
:输入值的整个数组:array('n' => 'foo', 'd' => 'bar', 'other' => 'baz', 'f' => false, 'verbose' => false)
。@Named('argv.n')
:选项 -n 的值,在这种情况下为 foo。@Named('argv.d')
:选项 -d 的值,在这种情况下为 bar。@Named('argv.other')
:选项 --other 的值,在这种情况下为 baz。@Named('argv.f')
:选项 -f 的值,在这种情况下为false
。@Named('argv.verbose')
:选项--verbose的值,在本例中为false
。
任何值为false
的原因是PHP的getopt()
函数将没有值的参数的值设置为false
。
请求未传递的值,例如本例中的@Named('argv.invalid')
,将导致stubbles\ioc\BindingException
。
控制台应用程序运行脚本
对于每个控制台应用程序,都应该有一个运行脚本,可以用来执行控制台应用程序。当使用createConsoleApp
脚本(见上文)时,此类脚本将自动创建。
测试控制台应用程序
将所有必需的代码放入应用程序类中有一个巨大的优势:你可以创建一个单元测试,确保可以创建包含所有依赖项的应用程序。这意味着你可以有一个这样的单元测试
/** * @test */ public function canCreateInstance() { $this->assertInstanceOf( MyConsoleApp::class, MyConsoleApp::create(new Rootpath()) ); }
此测试确保所有依赖项都已绑定,并且可以创建应用程序的实例。如果你还为创建的所有逻辑创建了单元测试,并运行了这些测试,你就可以相当有信心应用程序会正常工作。
使用createConsoleApp
创建的应用程序的测试
使用createConsoleApp
创建的单元测试已经提供了两个测试
- 一个确保在成功运行后
run()
方法返回退出代码0的测试。 - 最后,一个确保可以创建应用程序实例的测试(见上文如何实现)。
从这个点开始,扩展此单元测试以测试你在应用程序类中实现的逻辑应该相当容易。
从命令行读取
要从命令行读取用户输入,可以使用stubbles\console\ConsoleInputStream
。它是一个普通的输入流,可以从其中读取。
如果你想在你的类中注入这样的输入流,建议对stubbles\streams\InputStream
进行类型提示,并为此参数添加一个@Named('stdin')
注解。
/** * receive input stream to read from command line * * @param InputStream $in * @Named('stdin') */ public function __construct(InputStream $in) { $this->in = $in; }
向命令行写入
要将内容写入命令行,有两种可能:直接写入标准输出,或者写入错误输出。两种方式都实现为一个输出流。
如果你想在你的类中注入这样的输出流,建议对stubbles\streams\OutputStream
进行类型提示,并分别为这些参数添加@Named('stout')
或@Named('sterr')
注解。
/** * receive streams for standard and error out * * @param OutputStream $out * @param OutputStream $err * @Named{out}('stdout') * @Named{err}('stderr') */ public function __construct(OutputStream $out, OutputStream $err) { $this->out = $out; $this->err = $err; }
从命令行读取和写入
有时会有需要同时从命令行读取和写入的情况。这时stubbles\console\Console
就派上用场了。它提供了一个到stdin输入流、stdout和stderr输出流的门面,这样你只需要依赖一个类而不是三个类。该类提供了读取和写入的方法
prompt(string $message, ParamErrors $paramErrors = null): ValueReader
自版本2.1.0起可用。
将消息写入stdout并返回一个类似于读取请求参数的价值读取器。如果你需要在值验证过程中访问可能发生的错误消息,你需要提供stubbles\input\ParamErrors
,错误将累积在参数名称stdin下。
readValue(ParamErrors $paramErrors = null): ValueReader
自版本2.1.0起可用。
类似于prompt()
,但没有消息。
confirm(string $message, string $default = null): bool
自版本2.1.0起可用。
询问用户是否确认某事。重复消息直到用户输入y或n(不区分大小写)。如果提供了默认值且用户没有输入任何内容,则使用该默认值 - 如果默认值是y,则返回true
,否则返回false
。
read(int $length = 8192): string
从stdin读取输入。
readLine(int $length = 8192): string
从stdin读取输入,去除行结束符。
write(string|InputStream $bytes): Console
将消息写入stdout,如果是一个输入流,该流的 内容将复制到stdout。
writeLine(string $bytes): Console
向stdout写入一行。
writeEmptyLine(): Console
自2.6.0版本起可用。 向stdout写入一个空行。
writeError(string|InputStream $bytes): Console
将错误消息写入stderr,如果是一个输入流,该流的 内容将复制到stderr。
writeErrorLine(string $bytes): Console
向stderr写入一个错误消息行。
writeEmptyErrorLine(): Console
自2.6.0版本起可用。 向stderr写入一个空错误消息行。
命令行执行器
有时需要在您的应用程序内部运行另一个命令行程序。 stubbles/console 通过 stubbles\console\Executor
类提供了一种方便的方式来执行此操作。
它提供了三种不同的方式来运行命令行程序
execute(string $command, callable $out = null)
:这将简单地执行给定的命令。如果执行器接收到一个可调用对象,那么该对象将为命令的输出中的每一行执行。executeAsync(string $command): InputStream
:这将执行命令,但可以通过返回的InputStream
在以后读取命令的输出。outputOf(string $command): \Generator
:这将执行给定的命令,并返回一个 Generator,它将按顺序产生命令输出的每一行。(自6.0.0版本起可用。)
如果执行的命令返回的退出代码不是0,这被视为失败,导致抛出 \RuntimeException
。
重定向输出
如果您想将命令的输出重定向到要执行的命令,可以为上述列出的每个方法提供一个重定向作为可选的最后参数。默认情况下,命令的错误输出通过 2>&1 重定向到标准输出。
示例
运行命令,忽略其输出
$executor->execute('git clone git://github.com/stubbles/stubbles-console.git');
运行命令并检索输出
$executor->execute('git clone git://github.com/stubbles/stubbles-console.git', [$myOutputStream, 'writeLine']);
异步运行命令
$inputStream = $executor->executeAsync('git clone git://github.com/stubbles/stubbles-console.git'); // ... do some other work here ... while (!$inputStream->eof()) { echo $inputStream->readLine(); }
直接接收命令输出
foreach ($executor->outputOf('git clone git://github.com/stubbles/stubbles-console.git') as $line) { echo $line; }
在变量中收集输出
有时收集命令的输出到一个单独的变量就足够了。可以使用 stubbles\console\collect()
函数来实现这一点。
$out = ''; $executor->execute('git clone git://github.com/stubbles/stubbles-console.git', collect($out));
之后,$out
包含了命令的所有输出,由 PHP_EOL
分隔。或者,可以使用数组,数组的每个元素将是命令输出的每一行。