深入理解 WordPress `WP_CLICommandWith = Subcommands` 类的源码:如何构建带有子命令的复杂命令。

大家好,我是老码,今天咱们来聊聊 WordPress CLI (简称 WP-CLI) 里一个挺有意思的家伙:WP_CLICommandWithSubcommands 类。这家伙专门负责构建那些带有子命令的复杂命令,就像一个命令界的俄罗斯套娃,一层套一层,功能强大得很。

咱们先来热个身,想想我们平时用 WP-CLI 都干些啥?比如 wp plugin activatewp plugin deactivate,这里的 plugin 就是主命令,activatedeactivate 就是子命令。 今天,我们的目标就是搞明白,如何像 WP-CLI 的开发者一样,也写出这种酷炫的、带有子命令的命令。

一、WP_CLICommandWithSubcommands 是个啥?

简单来说,WP_CLICommandWithSubcommands 是一个抽象类,它继承自 WP_CLICommand。这意味着它已经具备了 WP_CLICommand 的所有能力(比如能被 WP-CLI 识别和执行),并在其基础上,增加了处理子命令的功能。

核心思想是,主命令本身并不直接执行什么操作,而是作为一个“容器”,将具体的任务分发给不同的子命令去执行。

二、构建带子命令的命令:理论先行

在动手写代码之前,先捋一下思路。我们需要做哪些事情?

  1. 定义主命令类: 这个类需要继承 WP_CLICommandWithSubcommands
  2. 注册子命令: 将子命令类注册到主命令类中。
  3. 定义子命令类: 这些类需要继承 WP_CLICommand,并且实现具体的业务逻辑。
  4. 处理子命令的调用: WP_CLICommandWithSubcommands 会自动处理子命令的调用,并将参数传递给相应的子命令类。

三、代码实战:一步一步来

咱们来创建一个名为 wp awesome 的主命令,它有两个子命令:wp awesome hellowp awesome goodbye

1. 定义主命令类

<?php

/**
 * Awesome 命令,演示如何使用子命令.
 */
class Awesome_Command extends WP_CLICommandWithSubcommands {

    /**
     * Awesome_Command constructor.
     */
    public function __construct() {
        parent::__construct();
    }
}

WP_CLI::add_command( 'awesome', 'Awesome_Command' );

这段代码做了什么?

  • 我们创建了一个名为 Awesome_Command 的类,它继承自 WP_CLICommandWithSubcommands
  • 构造函数里调用了父类的构造函数,这是必须的。
  • 使用 WP_CLI::add_command()awesome 命令注册到 WP-CLI 中,并指定了对应的类。

现在,如果你在命令行输入 wp awesome,WP-CLI 会告诉你这个命令存在,但是啥也干不了,因为它还没有子命令。

2. 定义子命令类

我们先定义 wp awesome hello 子命令:

<?php

/**
 *  打印 Hello, World!
 */
class Awesome_Hello_Command extends WP_CLICommand {

    /**
     *  打印 Hello, World!
     *
     * @subcommand hello
     */
    public function hello( $args, $assoc_args ) {
        WP_CLI::line( 'Hello, World!' );
    }
}

WP_CLI::add_command( 'awesome hello', 'Awesome_Hello_Command' );

这段代码定义了一个名为 Awesome_Hello_Command 的类,它继承自 WP_CLICommandhello() 方法就是具体的业务逻辑,它会打印 "Hello, World!"。 @subcommand hello 这个 DocBlock 注释非常重要,它告诉 WP-CLI 这个方法应该作为 awesome 命令的 hello 子命令来调用。

我们再定义 wp awesome goodbye 子命令:

<?php

/**
 *  打印 Goodbye, World!
 */
class Awesome_Goodbye_Command extends WP_CLICommand {

    /**
     *  打印 Goodbye, World!
     *
     * @subcommand goodbye
     */
    public function goodbye( $args, $assoc_args ) {
        WP_CLI::line( 'Goodbye, World!' );
    }
}

WP_CLI::add_command( 'awesome goodbye', 'Awesome_Goodbye_Command' );

这段代码和 Awesome_Hello_Command 类似,只是打印的内容变成了 "Goodbye, World!"。

3. 注册子命令到主命令(改进版)

现在,虽然我们已经定义了子命令类,并且使用 WP_CLI::add_command() 注册了它们,但是,按照 WP_CLICommandWithSubcommands 的设计思路,我们更希望 注册主命令,让它自己去发现和管理子命令。

所以,我们修改 Awesome_Command 类:

<?php

/**
 * Awesome 命令,演示如何使用子命令.
 */
class Awesome_Command extends WP_CLICommandWithSubcommands {

    /**
     * Awesome_Command constructor.
     */
    public function __construct() {
        parent::__construct();
        WP_CLI::add_command( 'awesome hello', 'Awesome_Hello_Command' );
        WP_CLI::add_command( 'awesome goodbye', 'Awesome_Goodbye_Command' );
    }
}

WP_CLI::add_command( 'awesome', 'Awesome_Command' );

在这个改进的版本中,我们在 Awesome_Command 的构造函数中,使用 WP_CLI::add_command() 注册了子命令。 这样,主命令就“知道”了它有哪些子命令。

更优雅的子命令注册方式:使用方法名称

WP_CLICommandWithSubcommands 还有一个更优雅的方式来注册子命令:使用方法名称。 我们可以直接在 Awesome_Command 类中定义方法,并使用 @subcommand 注释来标记它们。

首先,删除之前 Awesome_Hello_CommandAwesome_Goodbye_Command 类的定义,以及它们对应的 WP_CLI::add_command() 调用。

然后,修改 Awesome_Command 类:

<?php

/**
 * Awesome 命令,演示如何使用子命令.
 */
class Awesome_Command extends WP_CLICommandWithSubcommands {

    /**
     *  打印 Hello, World!
     *
     * @subcommand hello
     */
    public function hello( $args, $assoc_args ) {
        WP_CLI::line( 'Hello, World!' );
    }

    /**
     *  打印 Goodbye, World!
     *
     * @subcommand goodbye
     */
    public function goodbye( $args, $assoc_args ) {
        WP_CLI::line( 'Goodbye, World!' );
    }
}

WP_CLI::add_command( 'awesome', 'Awesome_Command' );

现在,Awesome_Command 类中包含了 hello()goodbye() 两个方法,它们分别对应 wp awesome hellowp awesome goodbye 两个子命令。 这种方式更加简洁,也更符合面向对象的编程思想。

4. 测试你的命令

将上面的代码保存到你的 WP-CLI 命令目录(通常是 ~/.wp-cli/commands),然后分别执行:

wp awesome hello
wp awesome goodbye

如果一切顺利,你应该能看到 "Hello, World!" 和 "Goodbye, World!" 被打印出来。

四、进阶:参数传递与处理

子命令肯定不能只是简单地打印字符串,它们需要处理参数。 WP_CLICommand 类已经提供了强大的参数处理机制。

1. 位置参数

位置参数是指按照顺序传递的参数。例如:

wp awesome greet 老码

这里的 "老码" 就是一个位置参数。

修改 Awesome_Command 类中的 hello() 方法,使其接受一个位置参数:

<?php

/**
 * Awesome 命令,演示如何使用子命令.
 */
class Awesome_Command extends WP_CLICommandWithSubcommands {

    /**
     *  打印 Hello, [name]!
     *
     * @subcommand hello
     * @synopsis <name>
     */
    public function hello( $args, $assoc_args ) {
        $name = $args[0];
        WP_CLI::line( "Hello, {$name}!" );
    }

    /**
     *  打印 Goodbye, World!
     *
     * @subcommand goodbye
     */
    public function goodbye( $args, $assoc_args ) {
        WP_CLI::line( 'Goodbye, World!' );
    }
}

WP_CLI::add_command( 'awesome', 'Awesome_Command' );
  • @synopsis <name> 这个 DocBlock 注释告诉 WP-CLI,hello 子命令接受一个名为 <name> 的位置参数。
  • $args 数组包含了所有位置参数,$args[0] 就是第一个位置参数,也就是我们传入的 "老码"。

现在,执行 wp awesome hello 老码,你应该能看到 "Hello, 老码!" 被打印出来。

2. 关联参数

关联参数是指以 --key=value 的形式传递的参数。例如:

wp awesome greet --name=老码 --age=30

这里的 --name=老码--age=30 就是关联参数。

修改 Awesome_Command 类中的 hello() 方法,使其接受一个关联参数:

<?php

/**
 * Awesome 命令,演示如何使用子命令.
 */
class Awesome_Command extends WP_CLICommandWithSubcommands {

    /**
     *  打印 Hello, [name]!
     *
     * @subcommand hello
     * @synopsis [--name=<name>]
     */
    public function hello( $args, $assoc_args ) {
        $name = WP_CLIUtilsget_flag_value( $assoc_args, 'name', 'World' );
        WP_CLI::line( "Hello, {$name}!" );
    }

    /**
     *  打印 Goodbye, World!
     *
     * @subcommand goodbye
     */
    public function goodbye( $args, $assoc_args ) {
        WP_CLI::line( 'Goodbye, World!' );
    }
}

WP_CLI::add_command( 'awesome', 'Awesome_Command' );
  • @synopsis [--name=<name>] 这个 DocBlock 注释告诉 WP-CLI,hello 子命令接受一个名为 --name 的关联参数。
  • $assoc_args 数组包含了所有关联参数,WP_CLIUtilsget_flag_value() 函数可以方便地获取关联参数的值,如果参数不存在,则返回默认值 "World"。

现在,执行 wp awesome hello --name=老码,你应该能看到 "Hello, 老码!" 被打印出来。 如果执行 wp awesome hello,则会打印 "Hello, World!"。

五、高级技巧:参数验证与提示

WP-CLI 还提供了强大的参数验证和提示功能,可以帮助用户正确地使用你的命令。

1. 参数验证

可以使用 @validate DocBlock 注释来指定一个验证函数,用于验证参数的值。

例如,我们希望 age 参数必须是一个数字:

<?php

/**
 * Awesome 命令,演示如何使用子命令.
 */
class Awesome_Command extends WP_CLICommandWithSubcommands {

    /**
     *  打印 Hello, [name]!
     *
     * @subcommand hello
     * @synopsis [--name=<name>] [--age=<age>]
     * @validate age is_numeric
     */
    public function hello( $args, $assoc_args ) {
        $name = WP_CLIUtilsget_flag_value( $assoc_args, 'name', 'World' );
        $age  = WP_CLIUtilsget_flag_value( $assoc_args, 'age' );
        WP_CLI::line( "Hello, {$name}!" );
        if ( $age ) {
            WP_CLI::line( "You are {$age} years old." );
        }
    }

    /**
     *  打印 Goodbye, World!
     *
     * @subcommand goodbye
     */
    public function goodbye( $args, $assoc_args ) {
        WP_CLI::line( 'Goodbye, World!' );
    }
}

WP_CLI::add_command( 'awesome', 'Awesome_Command' );
  • @validate age is_numeric 这个 DocBlock 注释告诉 WP-CLI,age 参数的值必须是一个数字。如果用户传递了一个非数字的值,WP-CLI 会显示一个错误信息。

2. 参数提示

可以使用 @suggest DocBlock 注释来为参数提供提示。

例如,我们希望为 name 参数提供一些建议的值:

<?php

/**
 * Awesome 命令,演示如何使用子命令.
 */
class Awesome_Command extends WP_CLICommandWithSubcommands {

    /**
     *  打印 Hello, [name]!
     *
     * @subcommand hello
     * @synopsis [--name=<name>]
     * @suggest  --name=老码,--name=小李,--name=张三
     */
    public function hello( $args, $assoc_args ) {
        $name = WP_CLIUtilsget_flag_value( $assoc_args, 'name', 'World' );
        WP_CLI::line( "Hello, {$name}!" );
    }

    /**
     *  打印 Goodbye, World!
     *
     * @subcommand goodbye
     */
    public function goodbye( $args, $assoc_args ) {
        WP_CLI::line( 'Goodbye, World!' );
    }
}

WP_CLI::add_command( 'awesome', 'Awesome_Command' );
  • @suggest --name=老码,--name=小李,--name=张三 这个 DocBlock 注释告诉 WP-CLI,当用户输入 wp awesome hello --name= 时,应该显示 "老码"、"小李" 和 "张三" 作为建议的值。

六、总结

今天我们深入探讨了 WP_CLICommandWithSubcommands 类,学习了如何构建带有子命令的复杂命令。

特性 描述
继承 主命令类必须继承自 WP_CLICommandWithSubcommands
子命令注册 可以通过在主命令类的构造函数中使用 WP_CLI::add_command() 注册子命令,也可以直接在主命令类中定义方法,并使用 @subcommand 注释来标记它们。
参数传递 位置参数通过 $args 数组传递,关联参数通过 $assoc_args 数组传递。 可以使用 WP_CLIUtilsget_flag_value() 函数方便地获取关联参数的值。
参数验证 可以使用 @validate DocBlock 注释来指定一个验证函数,用于验证参数的值。
参数提示 可以使用 @suggest DocBlock 注释来为参数提供提示。
DocBlock 注释的重要性 DocBlock 注释是 WP-CLI 理解命令结构和参数的关键。 @subcommand@synopsis@validate@suggest 等注释都非常重要。
优雅的子命令注册方式 直接在主命令类中使用 @subcommand 注释定义子命令方法,更加简洁和面向对象。

掌握了这些知识,你就可以轻松地构建出功能强大的 WP-CLI 命令,提高你的 WordPress 开发效率。

希望今天的讲座对你有所帮助!下次再见!

发表回复

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