探究 WordPress `WP_CLICommandWith = Subcommands` 类的源码:如何构建带有子命令的复杂命令。

咳咳,各位观众老爷们,晚上好!今天咱们不聊风花雪月,专啃硬骨头,来聊聊 WordPress 里一个有点意思的类:WP_CLICommandWithSubcommands

这玩意儿,说白了,就是让你能像玩俄罗斯套娃一样,把命令套命令,搞出一些结构复杂的命令行工具。 就像 wp user createuser 就是个“父命令”, create 就是它的“子命令”。

那,怎么用这东西搭积木呢? 咱们一点点来。

一、 为什么需要子命令?

在开始深入代码之前,先思考一下,为什么我们需要子命令? 难道一个命令不能解决所有问题吗?

当然不是!想象一下,如果你要管理 WordPress 的用户,你可能会需要:

  • 创建用户
  • 删除用户
  • 更新用户信息
  • 列出用户

如果把这些功能都塞到一个 wp user 命令里,那参数得有多少?用户得晕成什么样?

子命令的出现,就是为了解决这个问题。 它把复杂的功能拆分成更小的、更易于管理的单元,让命令行工具更加清晰、易用。

二、WP_CLICommandWithSubcommands 类的基本结构

WP_CLICommandWithSubcommands 类本身并不复杂,它主要做了两件事:

  1. 注册子命令: 告诉 WP-CLI,这个命令有孩子,并且这些孩子都是些什么东西。
  2. 分发请求: 当用户输入 wp parent child 时,它负责把请求交给正确的 child 命令去处理。

咱们先来看一个简单的例子:

<?php

namespace MyPlugin;

use WP_CLI;
use WP_CLICommandWithSubcommands;

/**
 * 管理我的小猫咪们.
 */
class CatsCommand extends CommandWithSubcommands {

    /**
     * 创建一只新的小猫咪.
     *
     * ## OPTIONS
     *
     * <name>
     * : 小猫咪的名字.
     *
     * [--color=<color>]
     * : 小猫咪的颜色.
     * ---
     * default: white
     * options:
     *   - white
     *   - black
     *   - ginger
     * ---
     *
     * ## EXAMPLES
     *
     *     wp cats create mittens --color=ginger
     *
     * @param array $args       Positional arguments.
     * @param array $assoc_args Associative arguments.
     */
    public function create( $args, $assoc_args ) {
        $name  = $args[0];
        $color = $assoc_args['color'];

        WP_CLI::success( "成功创建了一只名为 {$name} 的 {$color} 色小猫咪!" );
    }

    /**
     * 删除一只小猫咪.
     *
     * ## OPTIONS
     *
     * <name>
     * : 小猫咪的名字.
     *
     * ## EXAMPLES
     *
     *     wp cats delete mittens
     *
     * @param array $args       Positional arguments.
     * @param array $assoc_args Associative arguments.
     */
    public function delete( $args, $assoc_args ) {
        $name = $args[0];

        WP_CLI::success( "成功删除了名为 {$name} 的小猫咪!(呜呜呜)" );
    }
}

WP_CLI::add_command( 'cats', 'MyPluginCatsCommand' );

这段代码做了什么?

  1. 定义了一个 CatsCommand 类: 这个类继承了 WP_CLICommandWithSubcommands,意味着它将拥有子命令的能力。
  2. 定义了两个方法 createdelete: 这两个方法分别对应了两个子命令 createdelete。 注意,方法名就是子命令的名字!
  3. 使用了 PHPDoc 注释: WP_CLI 会解析这些注释,生成命令行的帮助信息。## OPTIONS 定义了参数,## EXAMPLES 定义了使用示例。
  4. 注册命令: WP_CLI::add_command( 'cats', 'MyPluginCatsCommand' )CatsCommand 类注册为 wp cats 命令。

现在,你就可以在命令行里输入 wp cats create mittens --color=ginger 或者 wp cats delete mittens 来管理你的小猫咪了!(当然,实际上它什么也没做,只是输出了一些信息。)

三、深入源码:WP_CLICommandWithSubcommands 的内部机制

虽然上面的例子很简单,但它背后隐藏着一些重要的机制。 咱们来扒一扒 WP_CLICommandWithSubcommands 的源码,看看它是怎么工作的。

WP_CLICommandWithSubcommands 类本身的代码并不多,关键在于它的 __construct() 方法和 call_subcommand() 方法(实际上 call_subcommand() 方法是在其父类 WP_CLI_Command 中定义的,但它对子命令的调用至关重要)。

  • __construct() 方法:

    这个方法的主要作用是注册子命令。 它会扫描当前类的所有方法,找到那些看起来像子命令的方法(方法名不是以下划线 _ 开头的),然后把它们注册到 WP-CLI 的命令列表中。

    public function __construct() {
        $reflection = new ReflectionClass( $this );
    
        foreach ( $reflection->getMethods() as $method ) {
            if ( '_' === substr( $method->name, 0, 1 ) ) {
                continue;
            }
    
            $command = WP_CLI:: prep_command( $this, $method->name );
    
            if ( ! $command ) {
                continue;
            }
    
            $this->subcommands[ $method->name ] = $command;
        }
    }

    这段代码做了什么?

    1. 创建反射对象: new ReflectionClass( $this ) 创建了一个反射对象,用于获取当前类的所有方法。
    2. 遍历所有方法: foreach ( $reflection->getMethods() as $method ) 遍历了所有方法。
    3. 过滤私有方法: if ( '_' === substr( $method->name, 0, 1 ) ) { continue; } 跳过以下划线开头的方法(通常认为是私有方法)。
    4. 准备命令: $command = WP_CLI:: prep_command( $this, $method->name ) 调用 WP_CLI::prep_command() 方法,将当前对象和方法名传递给它。 WP_CLI::prep_command() 会做一些准备工作,例如解析 PHPDoc 注释,生成命令行的帮助信息。
    5. 添加到子命令列表: $this->subcommands[ $method->name ] = $command; 将准备好的命令添加到 $this->subcommands 数组中。
  • call_subcommand() 方法:

    当用户输入 wp parent child 时,WP-CLI 会调用 parent 命令的 call_subcommand() 方法,然后 call_subcommand() 方法会负责找到 child 命令,并调用它。

    /**
     * Call a subcommand of this command.
     *
     * @param array $args       Positional arguments.
     * @param array $assoc_args Associative arguments.
     * @return mixed
     */
    public function call_subcommand( $args, $assoc_args ) {
        if ( empty( $args ) ) {
            WP_CLI::error( "请指定一个子命令." );
        }
    
        $subcommand_name = array_shift( $args );
    
        if ( ! isset( $this->subcommands[ $subcommand_name ] ) ) {
            WP_CLI::error( sprintf( "'%s %s' 不是一个已知的命令.", WP_CLI::get_runner()->get_command_invocation(), $subcommand_name ) );
        }
    
        $subcommand = $this->subcommands[ $subcommand_name ];
    
        return WP_CLI::run_command( $subcommand, $args, $assoc_args );
    }

    这段代码做了什么?

    1. 检查是否有子命令: if ( empty( $args ) ) { WP_CLI::error( "请指定一个子命令." ); } 如果用户没有输入子命令,就报错。
    2. 获取子命令名称: $subcommand_name = array_shift( $args ); 从参数列表中取出第一个参数,作为子命令的名称。
    3. 检查子命令是否存在: if ( ! isset( $this->subcommands[ $subcommand_name ] ) ) { ... } 检查 $this->subcommands 数组中是否存在指定的子命令。
    4. 获取子命令对象: $subcommand = $this->subcommands[ $subcommand_name ];$this->subcommands 数组中取出子命令对象。
    5. 运行子命令: return WP_CLI::run_command( $subcommand, $args, $assoc_args ); 调用 WP_CLI::run_command() 方法,运行子命令。

四、更复杂的例子:嵌套子命令

俄罗斯套娃可以套很多层,子命令也可以嵌套很多层。 咱们来看一个更复杂的例子,管理猫咪的食物:

<?php

namespace MyPlugin;

use WP_CLI;
use WP_CLICommandWithSubcommands;

/**
 * 管理我的小猫咪们.
 */
class CatsCommand extends CommandWithSubcommands {

    /**
     * 管理小猫咪的食物.
     */
    public function food() {
        // 这个方法什么也不做,只是为了注册 FoodCommand
    }

    /**
     * 创建一只新的小猫咪.
     *
     * ## OPTIONS
     *
     * <name>
     * : 小猫咪的名字.
     *
     * [--color=<color>]
     * : 小猫咪的颜色.
     * ---
     * default: white
     * options:
     *   - white
     *   - black
     *   - ginger
     * ---
     *
     * ## EXAMPLES
     *
     *     wp cats create mittens --color=ginger
     *
     * @param array $args       Positional arguments.
     * @param array $assoc_args Associative arguments.
     */
    public function create( $args, $assoc_args ) {
        $name  = $args[0];
        $color = $assoc_args['color'];

        WP_CLI::success( "成功创建了一只名为 {$name} 的 {$color} 色小猫咪!" );
    }

    /**
     * 删除一只小猫咪.
     *
     * ## OPTIONS
     *
     * <name>
     * : 小猫咪的名字.
     *
     * ## EXAMPLES
     *
     *     wp cats delete mittens
     *
     * @param array $args       Positional arguments.
     * @param array $assoc_args Associative arguments.
     */
    public function delete( $args, $assoc_args ) {
        $name = $args[0];

        WP_CLI::success( "成功删除了名为 {$name} 的小猫咪!(呜呜呜)" );
    }
}

/**
 * 管理小猫咪的食物.
 */
class FoodCommand extends CommandWithSubcommands {

    /**
     * 添加食物.
     *
     * ## OPTIONS
     *
     * <name>
     * : 食物的名字.
     *
     * ## EXAMPLES
     *
     *     wp cats food add fish
     *
     * @param array $args       Positional arguments.
     * @param array $assoc_args Associative arguments.
     */
    public function add( $args, $assoc_args ) {
        $name = $args[0];

        WP_CLI::success( "成功添加了 {$name} 给小猫咪!" );
    }

    /**
     * 删除食物.
     *
     * ## OPTIONS
     *
     * <name>
     * : 食物的名字.
     *
     * ## EXAMPLES
     *
     *     wp cats food delete fish
     *
     * @param array $args       Positional arguments.
     * @param array $assoc_args Associative arguments.
     */
    public function delete( $args, $assoc_args ) {
        $name = $args[0];

        WP_CLI::success( "成功从小猫咪的食物清单中移除了 {$name}!" );
    }
}

WP_CLI::add_command( 'cats', 'MyPluginCatsCommand' );
WP_CLI::add_command( 'cats food', 'MyPluginFoodCommand' );

在这个例子中:

  1. CatsCommand 类有一个 food 方法,但这个方法什么也不做。 它的唯一作用是告诉 WP-CLI,wp cats food 命令应该指向 FoodCommand 类。
  2. FoodCommand 类继承了 WP_CLICommandWithSubcommands,并且定义了 adddelete 两个子命令,用于管理猫咪的食物。
  3. WP_CLI::add_command( 'cats food', 'MyPluginFoodCommand' )FoodCommand 类注册为 wp cats food 命令。

现在,你可以使用 wp cats food add fishwp cats food delete fish 来管理猫咪的食物了。

五、一些小技巧和注意事项

  • 命令的命名: 命令的名字应该简洁明了,能够清晰地表达命令的作用。
  • 参数的定义: 使用 PHPDoc 注释来定义参数,可以生成漂亮的命令行帮助信息。
  • 错误处理: 在命令中添加适当的错误处理,可以提高用户体验。 例如,检查用户是否输入了必需的参数,或者在操作失败时给出明确的错误提示。
  • 命令的组织: 对于复杂的命令行工具,可以使用嵌套子命令来组织命令,使结构更加清晰。
  • 避免过度嵌套: 虽然子命令可以嵌套很多层,但是过度的嵌套会使命令变得难以理解和使用。 一般来说,嵌套不要超过三层。
  • 使用 WP_CLI::line(), WP_CLI::success(), WP_CLI::warning(), WP_CLI::error() 等方法来输出信息,而不是 echoprint 这些方法可以更好地控制输出的格式,并且可以支持颜色输出。
  • 使用 WP_CLI::confirm() 方法来进行确认操作。 例如,在删除用户之前,可以询问用户是否确认删除。
  • 可以使用 WP_CLI::log() 方法来记录日志信息。 这对于调试和排错非常有用。

六、表格总结

特性 描述 示例
CommandWithSubcommands 继承此类可以创建带有子命令的命令。 class MyCommand extends CommandWithSubcommands { ... }
子命令定义 子命令是通过类中的公共方法定义的。方法名就是子命令的名字。 public function create( $args, $assoc_args ) { ... } 定义了一个名为 create 的子命令。
PHPDoc 注释 使用 PHPDoc 注释来定义命令的描述、参数和示例。WP_CLI 会解析这些注释,生成命令行的帮助信息。 phpn/**n * 创建一只新的小猫咪.n *n * ## OPTIONSn * <name>n * : 小猫咪的名字.n */npublic function create( $args, $assoc_args ) { ... }
命令注册 使用 WP_CLI::add_command() 方法来注册命令。 WP_CLI::add_command( 'cats', 'MyPluginCatsCommand' ); 注册 wp cats 命令。
嵌套子命令 通过在一个命令类中定义一个空方法(仅用于注册),并将另一个命令类注册为该方法的子命令,可以实现嵌套子命令。 WP_CLI::add_command( 'cats food', 'MyPluginFoodCommand' ); 注册 wp cats food 命令。
错误处理 使用 WP_CLI::error() 方法来输出错误信息。 WP_CLI::error( "请输入小猫咪的名字。" );
确认操作 使用 WP_CLI::confirm() 方法来进行确认操作。 $confirmed = WP_CLI::confirm( "确定要删除这只小猫咪吗?" );
日志记录 使用 WP_CLI::log() 方法来记录日志信息。 WP_CLI::log( "正在创建小猫咪..." );

七、总结

WP_CLICommandWithSubcommands 类是一个强大的工具,可以帮助你构建结构复杂的命令行工具。 通过理解它的内部机制,并灵活运用各种技巧,你可以创建出易于使用、功能强大的 WordPress 命令行工具。 希望今天的讲解对你有所帮助! 记得给小猫咪们喂食哦!

好了,今天就到这里,下课!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注