WP-CLI 自定义命令开发指南
创建自己的 WP-CLI 命令可能比想象中容易。wp scaffold package(项目仓库)可以动态生成除命令本身之外的项目内容。
概览
WP-CLI 的目标是成为 WordPress 后台的完整替代方案:凡是想在后台执行的操作,都应该有相应的 WP-CLI 命令。命令是 WP-CLI 功能的原子单元,例如 wp plugin install 和 wp plugin activate。命令能够为复杂任务提供简单而精确的操作接口,因此对 WordPress 用户很有帮助。
但 WordPress 后台如同功能极其复杂的瑞士军刀,单凭一个项目不可能覆盖所有使用场景。因此 WP-CLI 一方面提供常用内置命令,另一方面提供丰富的内部 API,让第三方编写和注册自己的命令。
命令可以作为独立软件包分发,也可以随 WordPress 插件或主题提供。独立软件包可发布为 Composer 包,并通过 Packagist 发现;同样可以用 wp scaffold package 生成命令之外的配套内容。
软件包之于 WP-CLI,就像插件之于 WordPress,但两者的开发方式有所不同。尽管 WP-CLI 不断扩展其替代 /wp-admin 的能力,编写软件包时仍应先考虑如何使用 WP-CLI 内部 API,再考虑与 WordPress API 的交互。
命令类型
内置命令:
- 通常覆盖标准 WordPress 安装提供的功能,但也有例外,例如 wp search-replace。
- 不依赖插件、主题等其他组件。
- 由 WP-CLI 团队维护。
第三方命令:
- 可以在插件或主题中定义。
- 可以用 wp scaffold package 轻松生成独立项目骨架。
- 可以独立于插件和主题,以 Composer 包、Git 仓库、本地路径或 ZIP 文件的形式分发。
所有命令都应遵循文档规范。
命令的构成
WP-CLI 支持将可调用的类、函数或闭包注册为命令。内置命令与第三方命令都使用 WP_CLI::add_command() 注册。
命令的 synopsis(用法概要)定义它接受的位置参数和关联参数。例如 wp plugin install:
$ wp plugin install
usage: wp plugin install <plugin|zip|url>... [--version=<version>] [--force] [--activate] [--activate-network]
这里的 <plugin|zip|url>... 是位置参数;该命令可以多次接收同类位置参数,即待安装插件的 slug、ZIP 文件或 URL。[--version=<version>] 是关联参数,用于指定插件版本。定义外围的方括号表示该参数可选。
WP-CLI 还提供适用于所有命令的一组全局参数。例如加入 --debug 后,会显示所有 PHP 错误,并输出更详细的 WP-CLI 启动信息。
注册时必需的参数
WP_CLI::add_command() 要求两个参数:
$name:WP-CLI 命名空间内的命令名称,例如 plugin install 或 post list。$callable:命令实现,可以是可调用的类、函数或闭包。
以下四种 wp foo 在功能上等价:
// 1. Command is a function
function foo_command( $args ) {
WP_CLI::success( $args[0] );
}
WP_CLI::add_command( 'foo', 'foo_command' );
// 2. Command is a closure
$foo_command = function( $args ) {
WP_CLI::success( $args[0] );
}
WP_CLI::add_command( 'foo', $foo_command );
// 3. Command is a method on a class
class Foo_Command {
public function __invoke( $args ) {
WP_CLI::success( $args[0] );
}
}
WP_CLI::add_command( 'foo', 'Foo_Command' );
// 4. Command is a method on a class with constructor arguments
class Foo_Command {
protected $bar;
public function __construct( $bar ) {
$this->bar = $bar;
}
public function __invoke( $args ) {
WP_CLI::success( $this->bar . ':' . $args[0] );
}
}
$instance = new Foo_Command( 'Some text' );
WP_CLI::add_command( 'foo', $instance );
类与函数、闭包有一些不同:
- 类的公共方法会注册为子命令。例如 Foo 类的 bar() 方法会注册为 wp foo bar。
__invoke()是特殊方法。若类实现了它,命令名称会绑定到该方法,类中的其他方法就不会再注册为命令。
命令既可注册到独立的顶级命名空间(如 wp foo),也可成为现有命名空间的子命令(如 wp core foo)。后一种情况下,在命令定义中包含已有命名空间即可:
class Foo_Command {
public function __invoke( $args ) {
WP_CLI::success( $args[0] );
}
}
WP_CLI::add_command( 'core foo', 'Foo_Command' );
快速执行一次性脚本
如果只是为一次性任务编写短脚本,无需正式调用 WP_CLI::add_command() 注册,可以使用 wp eval-file。例如 simple-command.php:
<?php
WP_CLI::success( "The script has run!" );
运行 wp eval-file simple-command.php 即可执行它。若脚本不需要加载 WordPress,可加入 --skip-wordpress。
可选的注册参数
命令的配置选项可通过 PHPDoc 声明,也可作为 WP_CLI::add_command() 的第三个 $args 参数传入。
使用 PHPDoc
下面是一个完整示例:
<?php
/**
* Implements example command.
*/
class Example_Command {
/**
* Prints a greeting.
*
* ## OPTIONS
*
* <name>
* : The name of the person to greet.
*
* [--type=<type>]
* : Whether or not to greet the person with success or error.
* ---
* default: success
* options:
* - success
* - error
* ---
*
* ## EXAMPLES
*
* wp example hello Newman
*
* @when after_wp_load
*/
function hello( $args, $assoc_args ) {
list( $name ) = $args;
// Print the message with type
$type = $assoc_args['type'];
WP_CLI::$type( "Hello, $name!" );
}
}
WP_CLI::add_command( 'example', 'Example_Command' );
这段 PHPDoc 会从三个方面解释。
Shortdesc:简短描述
PHPDoc 第一行就是 shortdesc:
/**
* Prints a greeting.
Longdesc:详细描述
PHPDoc 中间部分就是 longdesc:
* ## OPTIONS
*
* <name>
* : The name of the person to greet.
*
* [--type=<type>]
* : Whether or not to greet the person with success or error.
* ---
* default: success
* options:
* - success
* - error
* ---
*
* ## EXAMPLES
*
* wp example hello Newman
longdesc 中定义的选项会解释为命令的 synopsis:
<name>是必需的位置参数;<name>...表示一个或多个;[<name>]表示可选;[<name>...]表示多个可选位置参数。[--type=<type>]是可选关联参数,默认值为 success,可接受 success 或 error。改成[--error]就成为可选布尔标志。[--field[=<value>]]允许参数有值或无值。例如全局参数--skip-plugins[=<plugins>]可以跳过全部插件,也可以只跳过逗号分隔列表中的插件。[--status=<status>...]的结尾省略号声明可重复的关联参数。同一标志多次出现(如 –status=active –status=inactive)时,值会收集为数组,$assoc_args['status']为[ 'active', 'inactive' ]。不带省略号时,多次传值只保留最后一个。布尔标志(如 [–verbose])始终保留最后一个值,无论是否带省略号,都不会收集成数组。
要接受任意数量的可选关联参数,可以使用 [--<field>=<value>]:
* [--<field>=<value>]
* : Allow unlimited number of associative parameters.
命令 synopsis 会在参数传给具体实现之前,用于校验参数。
参数别名
在 synopsis 的参数标记内,用 | 分隔并追加一个或多个别名。用户可以输入短形式或其他名称,WP-CLI 会自动将其映射到 $assoc_args 中的规范参数名:
* [--with-dependencies|w]
* : Include dependencies in the operation.
此定义同时接受 –with-dependencies 和 -w,两者都会填充 $assoc_args['with-dependencies']。
多个别名用额外的 | 分隔:
* [--verbose|v|wordy]
* : Enable verbose output.
带值的关联参数同样支持别名:
* [--format=<format>|f]
* : Output format.
wp example hello -f=json 等价于 wp example hello --format=json。短别名支持单连字符形式 -<alias>=value,参数名称支持双连字符形式 --<name>=value。
YAML 参数选项
参数描述之后的 --- 块允许使用 YAML 设置额外元数据。default 指定省略参数时采用的默认值:
* [--type=<type>]
* : Whether or not to greet the person with success or error.
* ---
* default: success
* options:
* - success
* - error
* ---
options 限制允许输入的值;不在列表中的值会触发校验错误。
sensitive 标记密码或 API 密钥等敏感参数。使用全局标志 –prompt 时,WP-CLI 会向标准输出记录即将运行的完整命令;被标记为 sensitive: true 的参数值会在该行中替换为 [REDACTED],防止敏感值出现在该日志中。
* [--password=<password>]
* : Database password.
* ---
* sensitive: true
* ---
longdesc 也会显示在 help 输出中,例如 wp help example hello。其语法使用 Markdown Extra,处理规则如下:
- 通常按自由格式文本处理,OPTIONS 和 EXAMPLES 只是常用且推荐的章节名,并非强制要求。
- 章节名(## NAME)会着色显示,不缩进。
- 其他内容缩进 2 个字符;选项描述再增加 2 个字符。
- 为了尽量利用行宽、避免下一行仅剩一两个单词,选项描述应在冒号和空格之后按 75 字符硬换行,其他内容按 90 字符硬换行。
更多排版细节见 WP-CLI 文档规范。
Docblock 标签
最后一部分紧接在 longdesc 后:
* @when after_wp_load
*/
支持以下标签。
@subcommand
有时方法名称不能直接等于子命令名称。原文以 PHP 保留字 list 为例,此时可通过 @subcommand 指定对外的子命令名称:
/**
* @subcommand list
*/
function _list( $args, $assoc_args ) {
...
}
/**
* @subcommand do-chores
*/
function do_chores( $args, $assoc_args ) {
...
}
@alias
给子命令添加另一种调用方式:
/**
* @alias hi
*/
function hello( $args, $assoc_args ) {
...
}
$ wp example hi Joe
Success: Hello, Joe!
@when
指定 WP-CLI 何时执行命令,支持所有已注册 WP-CLI 钩子。多数命令在 WordPress 加载之后执行,默认行为为:
@when after_wp_load
要在 WordPress 加载之前运行:
@when before_wp_load
多数 WP-CLI 钩子在 WordPress 加载之前触发。如果命令从插件或主题加载,那么 WordPress 此时已经加载完成,@when 实际上会被忽略,不再产生预期的提前执行效果。
@skipglobalargcheck
当命令定义的参数名称与已有全局参数(例如 –debug、–user、–quiet)冲突时,WP-CLI 会在注册阶段发出警告。它提醒作者避免全局参数优先于命令参数而造成混淆。
若有意复用全局参数名称,例如封装另一个使用相同标志的工具,可以用 @skipglobalargcheck 关闭该警告:
/**
* @skipglobalargcheck
* @when before_wp_load
*/
function my_command( $args, $assoc_args ) {
...
}
WP_CLI::add_command() 的第三个 $args 参数
PHPDoc 支持的配置也可以在注册时通过第三个参数传入:
$hello_command = function( $args, $assoc_args ) {
list( $name ) = $args;
$type = $assoc_args['type'];
WP_CLI::$type( "Hello, $name!" );
if ( isset( $assoc_args['honk'] ) ) {
WP_CLI::log( 'Honk!' );
}
};
WP_CLI::add_command( 'example hello', $hello_command, array(
'shortdesc' => 'Prints a greeting.',
'synopsis' => array(
array(
'type' => 'positional',
'name' => 'name',
'description' => 'The name of the person to greet.',
'optional' => false,
'repeating' => false,
),
array(
'type' => 'assoc',
'name' => 'type',
'description' => 'Whether or not to greet the person with success or error.',
'optional' => true,
'default' => 'success',
'options' => array( 'success', 'error' ),
),
array(
'type' => 'flag',
'name' => 'honk',
'optional' => true,
),
),
'when' => 'after_wp_load',
'longdesc' => '## EXAMPLES' . "\n\n" . 'wp example hello Newman',
) );
longdesc 属性会追加到根据 synopsis 生成的选项描述之后,因此适合用来补充使用示例。如果没有 synopsis,则直接使用 longdesc 作为描述。
命令内部实现
了解注册方法之后,就可以在回调内部实现所需功能。
接收参数
要处理运行时参数,应为可调用对象增加 $args 和 $assoc_args 两个参数:
function hello( $args, $assoc_args ) {
/* Code goes here*/
}
$args 保存全部位置参数:
$ wp example hello Joe Doe
WP_CLI::line( $args[0] ); // Joe
WP_CLI::line( $args[1] ); // Doe
$assoc_args 保存以 –key=value、–flag 或 –no-flag 形式定义的参数:
$ wp example hello --name='Joe Doe' --verbose --no-option
WP_CLI::line( $assoc_args['name'] ); // Joe Doe
WP_CLI::line( $assoc_args['verbose'] ); // true
WP_CLI::line( $assoc_args['option'] ); // false
两种参数也可以混合使用:
$ wp example hello --name=Joe foo --verbose bar
WP_CLI::line( $assoc_args['name'] ); // Joe
WP_CLI::line( $assoc_args['verbose'] ); // true
WP_CLI::line( $args[0] ); // foo
WP_CLI::line( $args[1] ); // bar
有效复用 WP-CLI 内部 API
假设需要查找多站点网络中所有未使用的主题(问题 #2523)。原文指出,在 WordPress 后台手工完成可能需要数小时甚至数天,而熟悉 WP-CLI 命令开发后,可能用 15 分钟或更短时间写出相应命令。这里的时间是原文情境估计,并非性能测试结果。
对应命令如下:
/**
* Find unused themes on a multisite network.
*
* Iterates through all sites on a network to find themes which aren't enabled
* on any site.
*/
$find_unused_themes_command = function() {
$response = WP_CLI::launch_self( 'site list', array(), array( 'format' => 'json' ), false, true );
$sites = json_decode( $response->stdout );
$unused = array();
$used = array();
foreach( $sites as $site ) {
WP_CLI::log( "Checking {$site->url} for unused themes..." );
$response = WP_CLI::launch_self( 'theme list', array(), array( 'url' => $site->url, 'format' => 'json' ), false, true );
$themes = json_decode( $response->stdout );
foreach( $themes as $theme ) {
if ( 'no' == $theme->enabled && 'inactive' == $theme->status && ! in_array( $theme->name, $used ) ) {
$unused[ $theme->name ] = $theme;
} else {
if ( isset( $unused[ $theme->name ] ) ) {
unset( $unused[ $theme->name ] );
}
$used[] = $theme->name;
}
}
}
WP_CLI\Utils\format_items( 'table', $unused, array( 'name', 'version' ) );
};
WP_CLI::add_command( 'find-unused-themes', $find_unused_themes_command, array(
'before_invoke' => function(){
if ( ! is_multisite() ) {
WP_CLI::error( 'This is not a multisite installation.' );
}
},
) );
它用到以下内部 API:
- WP_CLI::add_command() 将 find-unused-themes 注册到 $find_unused_themes_command 闭包。before_invoke 参数用于确认当前是多站点安装,否则报错。
- WP_CLI::error() 输出格式化错误并退出。
- WP_CLI::launch_self() 先创建进程获取全部站点,再获取每个站点的主题列表。
- WP_CLI::log() 向用户输出信息。
- WP_CLI\Utils\format_items() 在查找完成后输出未使用主题列表。
帮助信息的呈现
help 命令会呈现 PHPDoc 或注册定义,顺序为:简短描述、用法概要、详细描述(OPTIONS、EXAMPLES 等),最后是全局参数。
编写测试
WP-CLI 使用基于 Behat 的测试框架,也建议命令作者采用它。Behat 的优势是新测试容易编写,因而更容易真正落实;测试与命令交互的方式也与用户一致。
Behat 测试放在项目的 features/ 目录中。以下示例来自 features/cli-info.feature:
Feature: Review CLI information
Scenario: Get the path to the packages directory
Given an empty directory
When I run `wp cli info --format=json`
Then STDOUT should be JSON containing:
"""
{"wp_cli_packages_dir_path":"/tmp/wp-cli-home/.wp-cli/packages/"}
"""
When I run `WP_CLI_PACKAGES_DIR=/tmp/packages wp cli info --format=json`
Then STDOUT should be JSON containing:
"""
{"wp_cli_packages_dir_path":"/tmp/packages/"}
"""
功能测试通常遵循 Given 某种背景、When 用户执行某个操作、Then 应产生某个结果(以及其他结果)的结构。可以从 wp-cli/scaffold-package-command 开始。
全局添加命令
若希望自定义命令全局可用,又不想创建完整插件或软件包,可以使用 WP-CLI 的 require 配置选项。在全局配置文件(通常为 ~/.wp-cli/config.yml)中引入 PHP 文件:
require:
- ~/.wp-cli/commands.php
随后创建该文件并添加自定义命令:
<?php
WP_CLI::add_command( 'hello-world', function () {
WP_CLI::success( "Hello World!" );
} );
此文件注册的命令会在每次运行 WP-CLI 时全局可用,适合在所有 WordPress 项目中使用的个人辅助命令。若只需要单次调用加载,可以在命令行传入 --require=<path>,或设置 WP_CLI_REQUIRE 环境变量。
分发
命令开发完成后,可以通过两种常见方式分享。
随插件或主题提供
可以把命令放进插件或主题,根据 WP_CLI 常量是否存在且为真,有条件地加载和注册:
if ( defined( 'WP_CLI' ) && WP_CLI ) {
require_once dirname( __FILE__ ) . '/inc/class-plugin-cli-command.php';
}
也可用 cli_init 钩子注册命令。它在 WP-CLI Runner 启动期间触发,提供专门的注册时机,不需要检查 WP_CLI 常量:
/**
* Register custom WP-CLI commands using the cli_init hook.
*/
function myplugin_register_cli_commands() {
require_once dirname( __FILE__ ) . '/inc/class-plugin-cli-command.php';
WP_CLI::add_command( 'myplugin', 'MyPlugin_CLI_Command' );
}
add_action( 'cli_init', 'myplugin_register_cli_commands' );
两种方式均可。需要根据 WP-CLI 是否存在决定加载代码时,用常量判断;需要挂接到初始化过程的具体时机时,用 cli_init。
作为独立命令分发
独立命令可以从 Git 仓库、ZIP 文件或目录安装。技术上的要求是提供有效的 composer.json,包含 autoload 声明。建议加入 "type": "wp-cli-package",明确标识软件包类型。
以下是 server 命令的完整 composer.json 示例:
{
"name": "wp-cli/server-command",
"description": "Start a development server for WordPress",
"type": "wp-cli-package",
"homepage": "https://github.com/wp-cli/server-command",
"license": "MIT",
"authors": [
{
"name": "Package Maintainer",
"email": "packagemaintainer@homepage.com",
"homepage": "https://www.homepage.com"
}
],
"require": {
"php": ">=5.3.29"
},
"autoload": {
"files": [ "command.php" ]
}
}
注意 autoload 声明,它会加载 command.php。将有效的 composer.json 加入项目仓库后,用户就可以使用包管理器,从存放软件包的位置安装。
Git 仓库
向 package install 传入 Git 仓库的 HTTPS 或 SSH 地址:
# Installing the package using an HTTPS link
$ wp package install https://github.com/wp-cli/server-command.git
# Installing the package using an SSH link
$ wp package install git@github.com:wp-cli/server-command.git
ZIP 文件
向 wp package install 传入 ZIP 文件路径:
# Installing the package using a ZIP file
$ wp package install ~/Downloads/server-command-main.zip











暂无评论内容