pmjones/auto-shell

自动解析命令行字符串到命令类。

1.0.2 2023-09-26 19:51 UTC

This package is auto-updated.

Last update: 2024-08-27 22:39:29 UTC


README

PDS Skeleton PDS Composer Script Names

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.phpphp 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 识别主方法参数类型提示为 intfloatstringboolmixedarray,并将自动将来自命令行调用的值收集并转换为相应的类型。

对于 boolAutoShell 将这些参数值不区分大小写地转换为 true1, t, true, y, yes。同样,它将这些参数值不区分大小写地转换为 false0, f, false, n, no

对于 arrayAutoShell 将使用 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_REJECTEDVALUE_REQUIREDVALUE_OPTIONAL 之一。默认为 VALUE_REJECTED

    • 如果 VALUE_REJECTED,则选项 不允许 指定值;当指定选项时,其值始终为默认值(见下文)。
    • 如果 VALUE_REQUIRED,则选项 必须 指定值;
    • 如果 VALUE_OPTIONAL,则选项 可以 指定值;如果选项未指定值,其值为默认值(见下文)。
  • default:(混合类型)指定选项未指定值时的值。默认值为 true

  • multiple:(布尔值)当 true 时,选项可以多次指定,并且值将以数组的形式传递。(特别说明:如果 modeVALUE_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 参数中定义的选项名称。

根据上述示例,这意味着您不能在 CommonOptionsFooOptions 中同时定义(例如)-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 系统;这完全由您自己控制。

在入门时,您可能只想使用 echoprintf() 等类似的功能。然而,这可能会变得麻烦,尤其是在您想要开始自动化测试时。您需要缓冲所有命令输出,捕获它,然后读取它以断言输出正确性。

作为替代方案,您可以通过传递一个写入 STDOUT 和 STDERR 资源句柄的 psr/log 实现,例如 pmjones/stdlog。然后在测试中,您可以使用 php://memory 资源句柄实例化实现,并从内存中读取命令输出。

最后,您可能希望注入一个更强大的独立 CLI 输入/输出系统。据称 league/climate 很好,但我没有使用过它。