zfcampus / zf-console
创建和分发控制台命令的库
Requires
- php: ^5.6 || ^7.0
- psr/container: ^1.0
- zendframework/zend-console: ^2.6
Requires (Dev)
- phpunit/phpunit: ^5.7.25 || ^6.4.4
- zendframework/zend-coding-standard: ~1.0.0
- zendframework/zend-filter: ^2.7.1
- zendframework/zend-validator: ^2.8.1
Suggests
- zendframework/zend-filter: ^2.7.1; Useful for filtering/normalizing argument values
- zendframework/zend-validator: ^2.8.1; Useful for providing more thorough argument validation logic
README
仓库弃用 2019-12-05
此仓库不再维护。生态系统中的其他项目,如symfony/console,提供更强大和灵活的解决方案,我们建议用户针对这些运行时进行操作。
简介
zf-console
在 Zend\Console
的基础上提供了功能,特别是使用 Zend\Console
的 DefaultRouteMatcher
创建独立 PHP 控制台应用程序的方法。它包括内置的 "帮助" 和 "版本" 命令,以及颜色化(通过 Zend\Console
),以及支持 shell 自动完成。
需求
请参阅 composer.json 文件。
安装
运行以下 composer
命令
$ composer require zfcampus/zf-console
或者,手动将以下内容添加到您的 composer.json
文件中,在 require
部分中
"require": { "zfcampus/zf-console": "^1.3" }
然后运行 composer update
以确保模块已安装。
创建应用程序
使用 zf-console
编写的控制台应用程序由以下部分组成
- 定义控制台路由
- 将路由名称映射到 PHP 可调用对象
- 创建和运行应用程序
定义控制台路由
zf-console
中的路由通常是配置驱动的。每个路由都是一个关联数组,由以下成员组成
- name(必需):路由的名称。名称必须在应用程序中是唯一的。
- route(可选):要匹配的 "route" 或控制台参数(更多内容见下文);如果未指定,则使用 name。另外,如果路由不以 name 开头,则会在路由前添加 name(除非您选择退出此功能)。
- description(可选):给定路由的详细帮助描述。
- short_description(可选):给定路由的简短帮助描述,用于命令摘要。
- options_descriptions(可选):一个数组,包含选项名称/描述对,对应于路由匹配的参数。
- constraints(可选):一个数组,包含名称/正则表达式对,用于匹配参数时使用,对应于路由中的参数。如果正则表达式对给定的参数失败,则路由不会匹配。
- aliases(可选):一个别名/参数对的数组;如果提供了别名,则在匹配成功时将其作为命名参数返回。
- defaults(可选):对于未匹配的参数,返回默认值。
- filters(可选):一个名称/
Zend\Filter\FilterInterface
对的数组。提供的过滤器将在匹配名称的参数时使用,以过滤/规范化名称。 - validators(可选):一个名称/
Zend\Validator\ValidatorInterface
对的数组。提供的验证器将在匹配名称的参数时使用,以验证名称;验证失败将导致路由不匹配。 - handler(可选):一个PHP可调用函数,或者一个没有构造函数参数且可调用的类的名称;如果指定了此参数,且在
Dispatcher
中没有映射任何命令,则在调用时将使用此处理器来处理命令。 - prepend_command_to_route(可选):一个标志,如果指定,表示是否将命令名称添加到路由中。由于这是默认行为,因此只有布尔值false才有意义。
或者,您可以创建一个ZF\Console\Route
实例。签名类似
$route = new ZF\Console\Route( $name, $route, $constraints, // optional $defaults, // optional $aliases, // optional $filters, // optional $validators // optional ); $route->setDescription($description); $route->setShortDescription($shortDescription); $route->setOptionsDescription($optionsDescription);
在定义路由时,您需要提供一个数组或Traversable
对象,该对象包含路由配置数组或Route
实例(它们可以混合使用)。
我们建议将您的路由放入配置文件中
// config/routes.php return array( array( 'name' => 'self-update', 'description' => 'When executed via the Phar file, performs a self-update by querying the package repository. If successful, it will report the new version.', 'short_description' => 'Perform a self-update of the script', ), array( 'name' => 'build', 'route' => '<package> [--target=]', 'description' => 'Build a package, using <package> as the package filename, and --target as the application directory to be packaged.', 'short_description' => 'Build a package', 'options_descriptions' => array( '<package>' => 'Package filename to build', '--target' => 'Name of the application directory to package; defaults to current working directory', ), 'defaults' => array( 'target' => getcwd(), // default to current working directory ), 'handler' => 'My\Builder', ), );
关于路由
ZF\Console\Route
是Zend\Console\RouteMatcher\DefaultRouteMatcher
的扩展,遵循其路由定义和匹配的规则。一般来说,路由字符串将包含
- 文本参数(用于匹配的文本字符串;例如,
build
)- 文本标志(例如,
--help
、-h
等;标志没有关联的值)- 位置值参数(不使用标志的命名捕获;例如,
<email>
)- 值标志参数(即长选项,带有关联值;例如,'--target=')
大多数参数可以通过用方括号括起来使其可选(例如,'[--target=]'、'[<command>]')。
有关如何创建路由规范字符串的完整概述,请参阅ZF2控制台路由文档。
路由定义
请注意,默认情况下,路由名称将添加到您传递的
route
前。在上面的示例中,build
路由变为build <package> [--target=]
。如果您想明确,可以在路由定义中包含命令名称,或者通过将布尔值false作为prepend_command_to_route
标志的值来禁用添加命令名称。添加命令名称是为了明确命令名称到路由的映射——这在考虑使用帮助系统(以命令为中心)时尤其重要。
将路由映射到可调用函数
为了执行命令,您需要将路由名称映射到将分发它们的代码。通过其map()
方法,ZF\Console\Dispatcher
提供了定义此类映射的能力。
$dispatcher = new ZF\Console\Dispatcher; $dispatcher->map('some-command-name', $callable)
$callable
参数可以是任何PHP可调用函数。此外,您可以提供一个字符串类名,只要该类可以在没有构造函数参数的情况下实例化,并且定义了__invoke()
方法。
所有可调用函数都应预期最多两个参数
function (\ZF\Console\Route $route, \Zend\Console\Adapter\AdapterInterface $console) { }
此外,可调用函数应返回一个整数状态,用作应用程序的退出状态;0
表示成功,而任何其他值都表示失败。
在路由配置中定义可调用函数
如前所述,您还可以通过路由配置中的
handler
键提供处理路由的可调用函数。该参数的规则与map()
方法相同。直接映射到
Dispatcher
实例的可调用函数将优先于通过配置传递的可调用函数。
创建和运行应用程序
创建应用程序包括
- 设置或检索路由列表
- 设置调度地图
- 实例化应用程序
- 运行应用程序
以下示例中,我们假设类 My\SelfUpdate
和 My\Build
可自动加载,并且每个都定义了方法 __invoke()
。
use My\SelfUpdate; use Zend\Console\Console; use ZF\Console\Application; use ZF\Console\Dispatcher; require_once __DIR__ . '/vendor/autoload.php'; // Composer autoloader define('VERSION', '1.1.3'); $dispatcher = new Dispatcher(); $dispatcher->map('self-update', new SelfUpdate($version)); $dispatcher->map('build', 'My\Build'); $application = new Application( 'Builder', VERSION, include __DIR__ . '/config/routes.php', Console::getInstance(), $dispatcher ); $exit = $application->run(); exit($exit);
特性
zf-console
提供了多项“开箱即用”的功能。包括
- 使用报告
- 帮助信息报告
- 版本报告
- Shell 自动补全
- 异常处理
可以通过执行不带参数的应用程序或仅带 help
参数的应用程序来观察使用报告
$ ./script.php Builder, version 1.1.3 Available commands: autocomplete Command autocompletion setup build Build a package help Get help for individual commands self-update Perform a self-update of the script version Display the version of the script
可以通过执行 script help <命令名称>
来观察单个命令的帮助报告
$ ./script.php help self-update Builder, version 1.1.3 Usage: self-update Help: When executed via the Phar file, performs a self-update by querying the package repository. If successful, it will report the new version.
在命令后命名路由
我们建议在命令名称后命名路由。部分原因是这简化了查找匹配的路由定义,但更重要的是:如果用户指定了命令但没有指定其有效的参数,则将使用该命令为该路由提供帮助使用消息。
例如,在上面的示例中,如果我没有输入任何额外的参数,则将显示
build
命令的使用消息,因为命令和路由名称匹配。
可以通过执行 script --version
或 script -v
来观察版本报告
$ ./script --version Builder, version 1.1.3
您可以通过多种方式覆盖默认行为。
首先,您可以在创建 Application
实例之前,通过在您的 Dispatcher
实例中映射它们来覆盖 help
或 version
命令中的任何一个
$dispatcher->map('help', $myCustomHelpCommand); $dispatcher->map('version', $myVersionCommand);
其次,您可以使用 Application
实例的 setBanner()
和/或 setFooter()
方法设置自定义的标题和页脚。每个都接受一个字符串消息,或一个可调用的函数来显示消息;如果使用可调用的函数,它将传递 Console
实例作为唯一参数。
$application->setBanner('Some ASCI art for a banner!'); // string $application->setBanner(function ($console) { // callable $console->writeLine( $console->colorize('Builder', \Zend\Console\ColorInterface::BLUE) . ' - for building deployment packages' ); $console->writeLine(''); $console->writeLine('Usage:', \Zend\Console\ColorInterface::GREEN); $console->writeLine(' ' . basename(__FILE__) . ' command [options]'); $console->writeLine(''); }); $application->setFooter('Copyright 2014 Zend Technologies');
禁用横幅
默认情况下会显示横幅。在某些情况下,您可能不想显示它;例如,当将输出管道传输到另一个进程时。
从版本 1.3.0 开始,您可以使用以下方法禁用横幅输出
$application->setBannerDisabledForUserCommands(true);
此外,从 1.3.0 版本开始,您可以显式地将横幅和页脚都置为空
$application->setBanner(null); $application->setFooter(null);
自动补全
自动补全是许多登录外壳的有用功能。 zf-console
为 bash、zsh 以及任何以类似方式理解自动补全规则的 shell 提供自动补全支持。规则是按脚本生成的,使用 autocomplete
命令
$ ./script autocomplete
执行此操作将输出一个 shell 脚本,您可以将其保存并添加到您的工具链中;该脚本本身包含有关如何保存它以及如何将其添加到您的 shell 的信息。在大多数情况下,它看起来像这样
$ {script} autocomplete > > $HOME/bin/{script}_autocomplete.sh $ echo "source \$HOME/bin/{script}_autocomplete.sh" > > $HOME/{your_shell_rc}
其中 {script}
是命令的名称,而 {your_shell_rc}
是您的 shell 的运行时配置文件的位置(例如,.bashrc
、.zshrc
)。
调度程序可调用的函数
Dispatcher
将通过使用两个参数调用它与给定路由关联的可调用函数来调用该函数
- 匹配的
ZF\Console\Route
实例 - 当前正在使用的
Zend\Console
适配器
在大多数情况下,您将使用 Route
实例来收集传递给应用程序的参数,并使用 Console
实例提供任何反馈或提示任何附加信息。
Route
实例包含一些有趣的方法
getMatches()
将返回一个包含所有匹配的命名参数的数组。matchedParam($name)
将告诉您是否匹配了给定的参数。getMatchedParam($name, $default = null)
函数将返回给定参数的匹配值,如果没有匹配到,则返回您提供的$default
值。getName()
函数将返回路由的名称(如果使用相同的可调用对象为多个路由服务,这可能很有用)。
自定义调度器
- 自 1.3.0 版本起
您可以通过实现 ZF\Console\DispatcherInterface
来创建自定义调度器,该接口定义了以下方法
namespace ZF\Console; use Zend\Console\Adapter\AdapterInterface as ConsoleAdapter; interface DispatcherInterface { /** * Map a command name to its handler. * * @param string $command * @param callable|string $command A callable command, or a string service * or class name to use as a handler. * @return self Should implement a fluent interface. */ public function map($command, $callable); /** * Does the dispatcher have a handler for the given command? * * @param string $command * @return bool */ public function has($command); /** * Dispatch a routed command to its handler. * * @param Route $route * @param ConsoleAdapter $console * @return int The exit status code from the command. */ public function dispatch(Route $route, ConsoleAdapter $console); }
实现后,实例化您的自定义调度器,并在初始化时将其传递给 Application
实例。
$application = new Application('App', 1.0, $routes, null, $dispatcher);
从容器中拉取命令
- 自 1.3.0 版本起
除了指定命令处理器的可调用或类名外,您还可以将您的处理程序存储在兼容 PSR-11 规范 的依赖注入容器中;这样,您可以使用处理器的 服务名称 来指定。
为此,您需要创建一个 Dispatcher
实例,在实例化时传递您正在使用的容器。
$serviceManager = new ServiceManager(/* ... */); // use `zend-servicemanager` as container $dispatcher = new Dispatcher($serviceManager);
从那里,您可以使用服务名称(通常是类名)配置路由。
$routes = [
[
'name' => 'hello',
'handler' => HelloCommand::class,
]
];
最后,不要忘记在初始化时将您的调度器传递给您的应用程序。
$application = new Application('App', 1.0, $routes, null, $dispatcher);
在上面的示例中,当 hello
路由匹配时,Dispatcher
将在调度之前尝试从容器中拉取 HelloCommand
服务。
异常处理
zf-console
默认通过 ZF\Console\ExceptionHandler
提供异常处理。当您的控制台应用程序抛出异常时,此处理程序将提供错误的一个“美观”视图,而不是完整的堆栈跟踪(除非您想在视图中包含堆栈跟踪)!
默认消息如下所示
====================================================================== The application has thrown an exception! ====================================================================== :className: :message
其中 :className
将填充异常的类名,而 message
将包含任何异常消息。
如果您希望提供自己的模板,可以这样做。
$application->getExceptionHandler()->setMessageTemplate($template);
以下定义了以下模板变量
:className
:message
:code
:file
:line
:stack
:previous
(这用于报告跟踪中的上一个异常)
如果您想提供自己的异常处理程序,可以通过向 setExceptionHandler()
方法提供任何 PHP 可调用来实现。
$application->setExceptionHandler($handler);
调试模式
如果您想使用正常的 PHP 堆栈跟踪和错误报告,可以将应用程序置于调试模式。
$application->setDebug(true);
在 Zend Framework 2 应用程序中使用 zf-console
虽然 Zend Framework 2 将控制台功能集成到 MVC 中,但您可能想编写不使用 MVC 的脚本。例如,在不通过创建控制器、添加控制台配置等步骤的情况下编写特定于应用程序的脚本可能更容易。但是,您可能仍然希望访问模块中提供的服务,并希望能够尊重服务和配置覆盖。
为此,您需要首先启动应用程序。我们将假设您将脚本放在应用程序的 bin/
目录中,以本例为例。
use Zend\Console\Console; use Zend\Console\ColorInterface as Color; use ZF\Console\Application; use ZF\Console\Dispatcher; chdir(dirname(__DIR__)); require 'init_autoloader.php'; // grabs the Composer autoloader and/or ZF2 autoloader $application = Zend\Mvc\Application::init(require 'config/application.config.php'); $services = $application->getServiceManager(); $buildModel = $services->get('My\BuildModel'); $dispatcher = new Dispatcher(); $dispatcher->map('build', function ($route, $console) use ($buildModel) { $opts = $route->getMatches(); $result = $buildModel->build($opts['package'], $opts['target']); if (! $result) { $console->writeLine('Error building package!', Color::WHITE, Color::RED); return 1; } $console->writeLine('Finished building package ' . $opts['package'], Color::GREEN); return 0; }); $application = new Application( 'Builder', VERSION, array( array( 'name' => 'build', 'route' => 'build <package> [--target=]', 'description' => 'Build a package, using <package> as the package filename, and --target as the application directory to be packaged.', 'short_description' => 'Build a package', 'options_descriptions' => array( '<package>' => 'Package filename to build', '--target' => 'Name of the application directory to package; defaults to current working directory', ), 'defaults' => array( 'target' => getcwd(), // default to current working directory ), ), ), Console::getInstance(), $dispatcher ); $exit = $application->run(); exit($exit);
基本上,您调用的是 Zend\Mvc\Application::init()
,但不是它的 run()
方法。这确保了所有模块都已引导,这意味着所有配置都已加载和合并,所有服务都已连接,所有监听器都已附加。然后,您从 ServiceManager
中提取相关服务并将它们传递给您的控制台回调。
最佳实践
当使用 zf-console
创建应用程序时,我们建议以下实践。
使用 Zend\Console
创建输出
使用 Zend\Console
创建任何您发送的输出。这确保了输出可以在跨平台(包括类Unix系统和Windows)上工作。例如:
$dispatcher->map('some-command', function ($route, $console) {
$console->writeLine('Executing some-command!');
});
通过 Composer 安装您的脚本
您可以通过将您的脚本安装到 vendor/bin/
目录来告诉 Composer 安装您的脚本,这使得最终用户能够轻松地在自己的应用程序中定位和执行您的脚本。
{ "require": { "php": ">=5.3.23", "zfcampus/zf-console": "~1.0-dev" }, "bin": ["script.php"] }
如果您这样做,请确保您的脚本名称是唯一的。
使用过滤器或验证器
Zend\Console
的 RouteMatcher 子组件允许您为每个匹配的路由参数指定过滤器或验证器。这些允许您在需要时提供归一化(过滤器)和更健壮的验证逻辑。
例如,考虑一个常见的场景,即使用逗号分隔的值作为参数;您可以如下将其拆分为一个数组
// config/routes.php use Zend\Filter\Callback as CallbackFilter; return array( array( 'name' => 'filter', 'route' => 'filter [--exclude=]', 'default' => array( 'exclude' => array(), ), 'filters' => array( 'exclude' => new CallbackFilter(function ($value) { if (! is_string($value)) { return $value; } $exclude = explode(',', $value); array_walk($exclude, 'trim'); return $exclude; }), ), ) );
合理地使用过滤器和验证器,您可以确保在您的分发回调接收到数据时,它已经被清理并准备好使用。
由 zf-console 提供的过滤器
zf-console
为您的方便提供了以下过滤器
-
ZF\Console\Filter\Explode
允许您指定一个分隔符,用于将字符串值“爆炸”成一个值数组。例如:// config/routes.php use ZF\Console\Filter\Explode as ExplodeFilter; return array( array( 'name' => 'filter', 'route' => 'filter [--exclude=]', 'default' => array( 'exclude' => array(), ), 'filters' => array( 'exclude' => new ExplodeFilter(','), ), ) );
上面的代码会使用逗号分隔符将提供给
--exclude
的值拆分为数组;--exclude=foo,bar,baz
会将exclude
设置为array('foo', 'bar', 'baz')
。默认情况下,如果没有提供分隔符,则假定使用逗号。 -
ZF\Console\Filter\Json
允许您指定一个 JSON 格式的字符串;它将反序列化它到原生 PHP 值。// config/routes.php use ZF\Console\Filter\Json as JsonFilter; return array( array( 'name' => 'filter', 'route' => 'filter [--exclude=]', 'default' => array( 'exclude' => array(), ), 'filters' => array( 'exclude' => new JsonFilter(), ), ) );
上面的代码会反序列化提供给
--exclude
的 JSON 值;--exclude='["foo","bar","baz"]'
会将exclude
设置为array('foo', 'bar', 'baz')
。 -
ZF\Console\Filter\QueryString
允许您指定一个表单编码的字符串;它将反序列化它到原生 PHP 值。// config/routes.php use ZF\Console\Filter\QueryString; return array( array( 'name' => 'filter', 'route' => 'filter [--exclude=]', 'default' => array( 'exclude' => array(), ), 'filters' => array( 'exclude' => new QueryString(), ), ) );
上面的代码会反序列化提供给
--exclude
的表单编码的值;--exclude='foo=bar&baz=bat'
会将exclude
设置为array('foo' => 'bar', 'baz' => 'bat')
。
类
此库定义了以下类
ZF\Console\Application
,它处理脚本的实际执行,包括使用报告。ZF\Console\Dispatcher
,它将路由名称映射到 PHP 可调用项,并在选择时分发它们。ZF\Console\HelpCommand
,它提供默认的“帮助”逻辑,用于显示命令用法。ZF\Console\Route
,它是Zend\Console\RouteMatcher\DefaultRouteMatcher
的扩展,它添加了路由元数据的聚合,包括名称和描述。ZF\Console\RouteCollection
,它实现Zend\Console\RouteMatcher\RouteMatcherInterface
,聚合ZF\Console\Route
实例,并执行路由匹配。ZF\Console\Filter\Explode
,实现了Zend\Filter\FilterInterface
接口,并且如上所述。ZF\Console\Filter\Json
,实现了Zend\Filter\FilterInterface
接口,并且如上所述。ZF\Console\Filter\QueryString
,实现了Zend\Filter\FilterInterface
接口,并且如上所述。