大家好,欢迎来到今天的WP-CLI自定义命令编程讲座。今天咱们就来扒一扒 WP_CLI::add_command()
这个看似简单,实则暗藏玄机的函数,看看它到底是如何让我们的自定义命令在WP-CLI的世界里安家落户的。
开场白:WP-CLI的世界,命令的乐园
WP-CLI,WordPress Command-Line Interface,顾名思义,就是用命令行来管理WordPress。想象一下,不用登录后台,直接敲几行命令就能更新插件、导入数据、甚至清理垃圾文件,是不是很酷?而这一切,都离不开各种各样的命令。
WordPress本身已经内置了很多实用的命令,比如wp plugin install
,wp user create
等等。但是,总有些时候,我们需要一些定制化的功能,这时,就需要我们自己编写自定义命令了。而WP_CLI::add_command()
就是我们开启自定义命令之旅的钥匙。
正题:WP_CLI::add_command()
的源码解剖
WP_CLI::add_command()
的作用,简单来说,就是将一个PHP类或者函数注册为 WP-CLI 的一个命令。 咱们先看看它的基本用法:
WP_CLI::add_command( 'my-command', 'My_Command_Class' ); //类作为命令
WP_CLI::add_command( 'my-command', 'my_command_function' ); //函数作为命令
第一个参数 'my-command'
是命令的名称,第二个参数可以是类的名称或者函数的名称。 当我们在命令行输入 wp my-command
的时候,WP-CLI 就会执行对应的类或者函数。
那么,WP_CLI::add_command()
内部到底做了什么呢? 让我们一起深入源码一探究竟。(以下代码基于 WP-CLI 的源码,为了便于理解,进行了简化和注释)
<?php
class WP_CLI {
protected static $commands = array();
/**
* Registers a command with WP-CLI.
*
* @param string $name Name of the command.
* @param mixed $callable Either a callable or a class name.
* @param array $args Optional. Arguments to pass to the command.
*/
public static function add_command( $name, $callable, $args = array() ) {
if ( ! is_string( $name ) || empty( $name ) ) {
WP_CLI::error( 'Command name must be a non-empty string.' );
}
if ( ! is_callable( $callable ) && ! is_string( $callable ) ) {
WP_CLI::error( 'Command callable must be a callable or a class name.' );
}
$name = self::validate_command_name( $name );
self::$commands[ $name ] = array(
'callable' => $callable,
'args' => $args,
);
}
/**
* Validates the command name.
*
* @param string $name Command name.
* @return string Validated command name.
*/
protected static function validate_command_name( $name ) {
$name = trim( $name );
if ( strpos( $name, ':' ) !== false ) {
list( $parent, $child ) = explode( ':', $name, 2 );
$parent = self::validate_command_name( $parent );
$child = self::validate_command_name( $child );
$name = $parent . ':' . $child;
}
return $name;
}
/**
* Executes a command.
*
* @param array $args Command arguments.
* @param array $assoc_args Command associative arguments.
*/
public static function run_command( $args, $assoc_args = array() ) {
global $argv;
// Extract command name from arguments.
$command_name = array_shift( $args );
if ( ! isset( self::$commands[ $command_name ] ) ) {
WP_CLI::error( sprintf( "'%s' is not a registered WP-CLI command.", $command_name ) );
}
$command = self::$commands[ $command_name ];
$callable = $command['callable'];
$command_args = $command['args'];
try {
if ( is_string( $callable ) && class_exists( $callable ) ) {
// Instantiate the class.
$instance = new $callable( $args, $assoc_args, $command_args );
// Check if the command has a __invoke method.
if ( method_exists( $instance, '__invoke' ) ) {
$return = call_user_func( array( $instance, '__invoke' ), $args, $assoc_args );
} else {
WP_CLI::error( 'Command class must have a __invoke() method.' );
}
} elseif ( is_callable( $callable ) ) {
// Call the function.
$return = call_user_func( $callable, $args, $assoc_args, $command_args );
} else {
WP_CLI::error( 'Command callable must be a callable or a class name.' );
}
// Handle return value (simplified for demonstration).
if ( is_string( $return ) ) {
WP_CLI::line( $return );
} elseif ( is_array( $return ) ) {
WP_CLI::print_table( $return ); // Assuming WP_CLI::print_table exists
}
} catch ( Exception $e ) {
WP_CLI::error( $e->getMessage() );
}
}
public static function print_table( $data, $headers = null ) {
// Simplified table printing logic for demonstration.
if ( empty( $data ) ) {
return;
}
if ( $headers === null ) {
$headers = array_keys( reset( $data ) );
}
// Print headers
WP_CLI::line( implode( "t", $headers ) );
// Print data
foreach ( $data as $row ) {
$values = array();
foreach ( $headers as $header ) {
$values[] = isset( $row[ $header ] ) ? $row[ $header ] : '';
}
WP_CLI::line( implode( "t", $values ) );
}
}
public static function error( $message ) {
fwrite( STDERR, WP_CLI::colorize( "%RError: {$message}%nn" ) );
exit(1);
}
public static function line( $message = '' ) {
echo WP_CLI::colorize( $message ) . "n";
}
public static function colorize( $string ) {
// Simplified colorization logic for demonstration.
$string = str_replace( '%R', "33[31m", $string ); // Red
$string = str_replace( '%n', "33[0m", $string ); // Reset
return $string;
}
}
核心逻辑分析
-
注册命令 (
add_command
)add_command()
首先会检查传入的命令名称$name
和可调用对象$callable
是否有效。$name
必须是非空的字符串,$callable
必须是有效的可调用对象 (函数名或者类名)。- 然后,它会调用
validate_command_name()
函数对命令名称进行验证和规范化,主要是处理带冒号:
的子命令。 - 最后,它会将命令名称
$name
和对应的可调用对象$callable
以及其他参数$args
存储到$commands
数组中。$commands
是一个静态的数组,用于保存所有注册的命令。
我们可以用一个表格来总结一下
add_command()
函数的主要工作:步骤 描述 1. 参数验证 检查命令名称和可调用对象是否有效。 2. 名称规范化 使用 validate_command_name()
处理子命令。3. 存储命令 将命令信息存储到 $commands
数组中。 -
命令执行 (
run_command
)run_command()
函数是 WP-CLI 执行命令的核心。它接收命令行参数$args
和关联数组$assoc_args
。- 首先,它从
$args
中提取命令名称$command_name
。 - 然后,它会在
$commands
数组中查找对应的命令信息。 如果找不到,会报错。 - 接下来,它会判断
$callable
是类名还是函数名。- 如果是类名,它会实例化该类,并检查该类是否定义了
__invoke()
方法。 如果没有,会报错。 然后,它会调用__invoke()
方法,并将$args
和$assoc_args
传递给它。 - 如果是函数名,它会直接调用该函数,并将
$args
和$assoc_args
传递给它。
- 如果是类名,它会实例化该类,并检查该类是否定义了
- 最后,它会处理命令的返回值,并将其输出到命令行。 (这里为了简化,只处理了字符串和数组两种类型)。
我们也可以用一个表格来总结一下
run_command()
函数的主要工作:步骤 描述 1. 提取命令名称 从命令行参数中提取命令名称。 2. 查找命令 在 $commands
数组中查找对应的命令信息。3. 判断可调用对象 判断 $callable
是类名还是函数名。4. 执行命令 如果是类,则实例化并调用 __invoke()
方法;如果是函数,则直接调用。5. 处理返回值 处理命令的返回值,并将其输出到命令行。 -
命令名称验证 (
validate_command_name
)这个函数主要负责处理带有冒号
:
的命令名称,也就是子命令。 它会将父命令和子命令分开,并递归地调用自身进行验证,确保命令名称的格式正确。例如,对于命令名称
my-plugin:activate
,它会将my-plugin
和activate
分开,然后分别进行验证。
实战演练:创建一个简单的自定义命令
理论说了一大堆,不如来点实际的。 咱们来创建一个简单的自定义命令,来加深对 WP_CLI::add_command()
的理解。
假设我们想要创建一个名为 wp hello
的命令,当执行这个命令时,它会在命令行输出 "Hello, WP-CLI!"。
首先,我们需要创建一个 PHP 文件,比如 hello-command.php
,并将其放置在 WordPress 插件目录下,或者主题目录下,或者任何 WP-CLI 可以找到的地方(比如 ~/.wp-cli/commands 目录)。
<?php
if ( ! class_exists( 'WP_CLI' ) ) {
return;
}
class Hello_Command {
/**
* Says hello to WP-CLI.
*
* ## EXAMPLES
*
* wp hello
*
* @when before_wp_load
*/
public function __invoke( $args, $assoc_args ) {
WP_CLI::line( 'Hello, WP-CLI!' );
}
}
WP_CLI::add_command( 'hello', 'Hello_Command' );
这段代码做了以下几件事情:
- 检查 WP-CLI 是否存在:
if ( ! class_exists( 'WP_CLI' ) ) { return; }
确保只有在 WP-CLI 环境下才会加载这段代码。 - 定义一个类
Hello_Command
:这个类就是我们自定义命令的执行主体。 - 定义
__invoke()
方法:这是 WP-CLI 执行命令时调用的方法。 所有的逻辑都写在这里。$args
和$assoc_args
分别是命令行参数和关联数组参数。 - 使用
WP_CLI::line()
输出信息:WP_CLI::line()
是 WP-CLI 提供的用于在命令行输出信息的函数。 - 使用
WP_CLI::add_command()
注册命令:WP_CLI::add_command( 'hello', 'Hello_Command' );
将Hello_Command
类注册为hello
命令。
现在,打开你的命令行,进入 WordPress 站点目录,然后输入 wp hello
,你应该可以看到 "Hello, WP-CLI!" 输出在命令行上。
进阶:带参数的自定义命令
光说 "Hello, WP-CLI!" 显然不够酷。 咱们来让这个命令更强大一点,让它可以接受一个参数,并向指定的人打招呼。
修改 hello-command.php
文件:
<?php
if ( ! class_exists( 'WP_CLI' ) ) {
return;
}
class Hello_Command {
/**
* Says hello to someone.
*
* ## OPTIONS
*
* <name>
* : The name of the person to greet.
*
* ## EXAMPLES
*
* wp hello John
* wp hello --name=Jane
*
* @when before_wp_load
*/
public function __invoke( $args, $assoc_args ) {
if ( ! empty( $args ) ) {
$name = $args[0];
} elseif ( isset( $assoc_args['name'] ) ) {
$name = $assoc_args['name'];
} else {
$name = 'WP-CLI';
}
WP_CLI::line( "Hello, {$name}!" );
}
}
WP_CLI::add_command( 'hello', 'Hello_Command' );
这次,我们做了以下修改:
- 添加了参数说明:
## OPTIONS
和## EXAMPLES
用于生成 WP-CLI 的帮助文档。 - 获取参数:我们在
__invoke()
方法中,首先检查$args
是否为空。 如果不为空,则从$args[0]
中获取参数。 否则,检查$assoc_args['name']
是否存在。 如果存在,则从$assoc_args['name']
中获取参数。 如果都没有,则使用默认值 "WP-CLI"。 - 输出个性化问候语:
WP_CLI::line( "Hello, {$name}!" );
根据获取到的参数,输出个性化的问候语。
现在,你可以尝试以下命令:
wp hello John
输出 "Hello, John!"wp hello --name=Jane
输出 "Hello, Jane!"wp hello
输出 "Hello, WP-CLI!"
更进一步:子命令
WP-CLI 还支持子命令,也就是命令后面再跟一个命令。 例如 wp plugin install
中的 install
就是 plugin
命令的子命令。
要创建子命令,只需要在 WP_CLI::add_command()
中使用冒号 :
分隔父命令和子命令即可。
例如,我们可以创建一个名为 wp my-plugin:activate
的命令:
<?php
if ( ! class_exists( 'WP_CLI' ) ) {
return;
}
class My_Plugin_Command {
/**
* Activates a plugin.
*
* ## OPTIONS
*
* <plugin>
* : The plugin to activate.
*
* ## EXAMPLES
*
* wp my-plugin activate my-plugin/my-plugin.php
*
* @when before_wp_load
*/
public function activate( $args, $assoc_args ) {
if ( empty( $args ) ) {
WP_CLI::error( 'Please specify a plugin to activate.' );
}
$plugin = $args[0];
// Simulate plugin activation.
WP_CLI::line( "Activating plugin: {$plugin}..." );
sleep(1);
WP_CLI::success( "Plugin {$plugin} activated." );
}
}
WP_CLI::add_command( 'my-plugin', 'My_Plugin_Command' );
if ( class_exists( 'My_Plugin_Command' ) ) {
WP_CLI::add_command( 'my-plugin activate', array( new My_Plugin_Command, 'activate' ) );
}
注意,这里我们注册了两个命令: my-plugin
和 my-plugin activate
。
WP_CLI::add_command( 'my-plugin', 'My_Plugin_Command' );
将My_Plugin_Command
类注册为my-plugin
命令。 执行wp my-plugin
会实例化My_Plugin_Command
类,并调用__invoke()
方法 (如果存在)。WP_CLI::add_command( 'my-plugin activate', array( new My_Plugin_Command, 'activate' ) );
将My_Plugin_Command
类的activate
方法注册为my-plugin activate
命令。 执行wp my-plugin activate
会调用My_Plugin_Command
类的activate
方法。
现在,你可以尝试输入 wp my-plugin activate my-plugin/my-plugin.php
,你应该可以看到模拟的插件激活过程。
高级技巧:使用 add_command()
的第三个参数
WP_CLI::add_command()
的第三个参数 $args
是一个可选的数组,可以用来传递一些额外的参数给命令。 这些参数可以在命令的执行过程中使用。
例如:
<?php
if ( ! class_exists( 'WP_CLI' ) ) {
return;
}
class My_Command {
/**
* A command that uses extra arguments.
*
* ## EXAMPLES
*
* wp my-command
*
* @when before_wp_load
*/
public function __invoke( $args, $assoc_args, $command_args ) {
WP_CLI::line( "Extra argument: " . $command_args['message'] );
}
}
WP_CLI::add_command( 'my-command', 'My_Command', array( 'message' => 'Hello from extra arguments!' ) );
在这个例子中,我们向 add_command()
传递了一个 $args
数组,其中包含一个 'message'
键值对。 在 __invoke()
方法中,我们可以通过 $command_args['message']
来获取这个参数的值。
运行 wp my-command
,你会看到 "Extra argument: Hello from extra arguments!"。
总结:WP_CLI::add_command()
的精髓
WP_CLI::add_command()
是 WP-CLI 自定义命令的核心函数。 它负责将 PHP 类或函数注册为 WP-CLI 的命令,并将其存储到 $commands
数组中。 当执行命令时,WP-CLI 会从 $commands
数组中查找对应的命令信息,并执行相应的类或函数。
理解 WP_CLI::add_command()
的源码,可以帮助我们更好地理解 WP-CLI 的工作原理,并编写更强大的自定义命令。
希望今天的讲座对你有所帮助。 记住,实践是检验真理的唯一标准。 多写代码,多尝试,你就能成为 WP-CLI 自定义命令的大师! 谢谢大家!