各位观众,大家好! 今天咱们来聊聊 WordPress REST API 的核心之一:register_rest_route()
函数。 别看这函数名字平平无奇,它可是连接 WordPress 后台和前端(或者任何外部应用)的关键桥梁。 咱们的目标是彻底搞懂它的工作原理,就像庖丁解牛一样,把它的源码拆开,一块一块地分析。
一、REST API 的基石:WP_REST_Server
类
首先,我们要认识一下 WordPress REST API 的总指挥官:WP_REST_Server
类。 这个类负责处理所有的 API 请求,包括路由匹配、权限验证、数据处理等等。 可以把它想象成一个交通枢纽,所有的 API 请求都要经过它来调度。
在 wp-includes/rest-api/class-wp-rest-server.php
文件中,你可以找到这个类的定义。 它有一个很重要的属性:$endpoints
。 这个属性是一个关联数组,存储了所有已注册的 REST API 路由。 键是路由的正则表达式,值是一个包含路由处理函数和其他配置信息的数组。
二、register_rest_route()
函数:路由注册员
register_rest_route()
函数的作用,就是把我们定义的 REST API 路由添加到 WP_REST_Server
类的 $endpoints
属性中。 它的原型如下:
/**
* Registers a REST route.
*
* @since 4.4.0
*
* @global WP_REST_Server $wp_rest_server REST server instance.
*
* @param string $namespace The namespace for the route.
* @param string $route The route to register.
* @param array $args Optional. An array of arguments to configure the route.
* @return bool True on success, false on failure.
*/
function register_rest_route( $namespace, $route, $args = array() ) {
global $wp_rest_server;
if ( ! ( $wp_rest_server instanceof WP_REST_Server ) ) {
return false;
}
return $wp_rest_server->register_route( $namespace, $route, $args );
}
可以看到,register_rest_route()
函数本身只是一个简单的包装器。 它首先检查 $wp_rest_server
全局变量是否是 WP_REST_Server
类的实例,如果不是,就返回 false
。 然后,它调用 $wp_rest_server
对象的 register_route()
方法,真正完成路由注册的工作。
三、WP_REST_Server::register_route()
方法:路由注册的核心
现在,让我们深入到 WP_REST_Server::register_route()
方法的源码中,看看它是如何工作的。
/**
* Registers a REST route.
*
* @since 4.4.0
*
* @param string $namespace The namespace for the route.
* @param string $route The route to register.
* @param array $args Optional. An array of arguments to configure the route.
* @return bool True on success, false on failure.
*/
public function register_route( $namespace, $route, $args ) {
// Validate arguments.
if ( ! is_string( $namespace ) ) {
_doing_it_wrong( __METHOD__, 'Namespace must be a string.', '4.4.0' );
return false;
}
if ( ! is_string( $route ) ) {
_doing_it_wrong( __METHOD__, 'Route must be a string.', '4.4.0' );
return false;
}
if ( ! is_array( $args ) ) {
_doing_it_wrong( __METHOD__, 'Args must be an array.', '4.4.0' );
return false;
}
// Normalize the route.
if ( empty( $route ) || '/' !== substr( $route, 0, 1 ) ) {
$route = '/' . $route;
}
$route = preg_replace( '#/+#', '/', $route );
// Normalize the namespace.
if ( empty( $namespace ) ) {
$namespace = 'wp/v2';
}
$namespace = trim( $namespace, '/' );
// Add the namespace to the route.
$full_route = '/' . $namespace . $route;
// Generate the regex for the route.
$regex = $this->get_route_regex( $full_route );
// Normalize the arguments.
$defaults = array(
'methods' => 'GET',
'callback' => null,
'args' => array(),
'permission_callback' => '__return_true',
);
$args = wp_parse_args( $args, $defaults );
// Validate the arguments.
if ( ! is_callable( $args['callback'] ) ) {
_doing_it_wrong( __METHOD__, 'Callback must be callable.', '4.4.0' );
return false;
}
if ( ! is_callable( $args['permission_callback'] ) ) {
_doing_it_wrong( __METHOD__, 'Permission callback must be callable.', '4.7.0' );
return false;
}
// Store the route.
if ( ! isset( $this->endpoints[ $regex ] ) ) {
$this->endpoints[ $regex ] = array();
}
$this->endpoints[ $regex ][] = array(
'methods' => $this->normalize_methods( $args['methods'] ),
'callback' => $args['callback'],
'args' => $args['args'],
'permission_callback' => $args['permission_callback'],
);
return true;
}
这个方法看起来有点长,但其实逻辑并不复杂。 咱们把它分解成几个步骤:
-
参数验证:
- 首先,验证
namespace
、route
和args
参数的类型是否正确。 如果类型不正确,就使用_doing_it_wrong()
函数输出一个警告,并返回false
。
- 首先,验证
-
路由和命名空间规范化:
- 确保路由以
/
开头,并移除多余的/
。 - 如果命名空间为空,则默认为
wp/v2
。 - 移除命名空间两侧的
/
。 - 将命名空间添加到路由前面,生成完整的路由
full_route
。
- 确保路由以
-
生成路由正则表达式:
- 调用
$this->get_route_regex( $full_route )
方法,将full_route
转换成一个正则表达式。 这个正则表达式用于匹配实际的 API 请求。
- 调用
-
参数规范化:
- 使用
wp_parse_args()
函数,将args
数组与默认值合并。 默认值包括:methods
:GET
(允许的 HTTP 方法)callback
:null
(路由处理函数)args
:array()
(路由参数)permission_callback
:__return_true
(权限验证函数,默认允许所有访问)
- 使用
-
参数验证:
- 验证
callback
和permission_callback
参数是否是可调用函数。 如果不是,就使用_doing_it_wrong()
函数输出一个警告,并返回false
。
- 验证
-
存储路由:
-
将路由信息存储到
$this->endpoints
属性中。$this->endpoints
是一个关联数组,键是路由的正则表达式,值是一个数组,包含了所有匹配该路由的路由信息。 -
如果
$this->endpoints[ $regex ]
不存在,则创建一个新的数组。 -
将包含
methods
、callback
、args
和permission_callback
的路由信息添加到$this->endpoints[ $regex ]
数组中。
-
-
返回成功:
- 返回
true
,表示路由注册成功。
- 返回
四、WP_REST_Server::get_route_regex()
方法:正则表达式生成器
WP_REST_Server::get_route_regex()
方法负责将路由字符串转换成正则表达式。 它的源码如下:
/**
* Retrieves the route regex for a route.
*
* @since 4.4.0
*
* @param string $route Route to generate the regex for.
* @return string Regex to match the requested route.
*/
protected function get_route_regex( $route ) {
$route = preg_replace( '#/+#', '/', $route );
$route = str_replace( array( '(', ')' ), array( '(', ')' ), $route );
$route = preg_replace( '#<([a-zA-Z_][a-zA-Z0-9_]*)>#', '(?P<$1>[^/]+)', $route );
$route = str_replace( '-', '-', $route );
return '#^' . $route . '$#';
}
这个方法的工作原理如下:
-
规范化路由:
- 移除多余的
/
。
- 移除多余的
-
转义括号:
- 将
(
和)
替换成(
和)
,避免正则表达式解析错误。
- 将
-
参数占位符替换:
- 将
<参数名>
替换成(?P<参数名>[^/]+)
。 这是一个命名捕获组,用于匹配除/
之外的任意字符,并将匹配到的值存储到名为参数名
的变量中。 例如,<id>
会被替换成(?P<id>[^/]+)
。
- 将
-
转义连字符:
- 将
-
替换成-
,避免正则表达式解析错误。
- 将
-
添加正则表达式定界符和边界:
- 在路由字符串的前后添加
#^
和$
,表示正则表达式的开始和结束。
- 在路由字符串的前后添加
举个例子:
假设我们要注册一个路由 /books/<id>
,那么 $this->get_route_regex()
方法会把它转换成如下的正则表达式:
#^/books/(?P<id>[^/]+)$#
这个正则表达式可以匹配 /books/123
、/books/456
等等。 匹配到的 id
值会被存储到 id
变量中,可以在路由处理函数中通过 $request->get_param( 'id' )
获取。
五、WP_REST_Server::normalize_methods()
方法:HTTP 方法标准化
WP_REST_Server::normalize_methods()
方法负责将 HTTP 方法字符串转换成一个大写字母的数组。 它的源码如下:
/**
* Normalizes method names to uppercase.
*
* @since 4.7.0
*
* @param string|array $methods HTTP methods to normalize.
* @return array Normalized array of methods in uppercase.
*/
protected function normalize_methods( $methods ) {
if ( ! is_array( $methods ) ) {
$methods = array( $methods );
}
$methods = array_map( 'strtoupper', $methods );
return $methods;
}
这个方法的工作原理如下:
-
转换为数组:
- 如果
$methods
不是数组,则将其转换为包含该方法的数组。
- 如果
-
转换为大写:
- 使用
array_map()
函数和strtoupper()
函数,将数组中的所有元素转换为大写。
- 使用
-
返回标准化后的数组:
- 返回包含大写 HTTP 方法的数组。
举个例子:
假设我们传入的 $methods
是 'get,post'
,那么 $this->normalize_methods()
方法会把它转换成 array( 'GET', 'POST' )
。
六、一个完整的例子:注册一个自定义 REST API 路由
现在,让我们通过一个完整的例子,来演示如何使用 register_rest_route()
函数注册一个自定义 REST API 路由。
add_action( 'rest_api_init', function () {
register_rest_route( 'myplugin/v1', '/books', array(
'methods' => 'GET',
'callback' => 'my_awesome_get_books',
'permission_callback' => '__return_true',
) );
register_rest_route( 'myplugin/v1', '/books/(?P<id>d+)', array(
'methods' => 'GET',
'callback' => 'my_awesome_get_book',
'args' => array(
'id' => array(
'validate_callback' => 'is_numeric',
'sanitize_callback' => 'absint',
),
),
'permission_callback' => '__return_true',
) );
register_rest_route( 'myplugin/v1', '/books', array(
'methods' => 'POST',
'callback' => 'my_awesome_create_book',
'permission_callback' => function() {
return current_user_can( 'publish_posts' );
},
'args' => array(
'title' => array(
'required' => true,
'type' => 'string',
'description' => 'The title of the book.',
),
'author' => array(
'type' => 'string',
'description' => 'The author of the book.',
),
),
) );
} );
function my_awesome_get_books( WP_REST_Request $request ) {
// 获取所有书籍
$books = get_posts( array( 'post_type' => 'book' ) );
$data = array();
foreach ( $books as $book ) {
$data[] = array(
'id' => $book->ID,
'title' => $book->post_title,
);
}
return $data;
}
function my_awesome_get_book( WP_REST_Request $request ) {
$id = $request->get_param( 'id' );
// 获取指定 ID 的书籍
$book = get_post( $id );
if ( empty( $book ) ) {
return new WP_Error( 'book_not_found', 'Book not found', array( 'status' => 404 ) );
}
$data = array(
'id' => $book->ID,
'title' => $book->post_title,
);
return $data;
}
function my_awesome_create_book( WP_REST_Request $request ) {
$title = $request->get_param( 'title' );
$author = $request->get_param( 'author' );
$post_id = wp_insert_post( array(
'post_title' => $title,
'post_content' => '',
'post_status' => 'publish',
'post_type' => 'book',
) );
if ( is_wp_error( $post_id ) ) {
return $post_id;
}
if ( ! empty( $author ) ) {
update_post_meta( $post_id, 'author', $author );
}
return rest_ensure_response( array( 'id' => $post_id, 'message' => 'Book created successfully' ) );
}
在这个例子中,我们注册了三个 REST API 路由:
GET /myplugin/v1/books
: 获取所有书籍。GET /myplugin/v1/books/<id>
: 获取指定 ID 的书籍。<id>
是一个参数占位符,会被替换成(?P<id>d+)
正则表达式。POST /myplugin/v1/books
: 创建一本书籍。
我们还定义了三个路由处理函数:my_awesome_get_books()
、my_awesome_get_book()
和 my_awesome_create_book()
。 这些函数负责处理实际的 API 请求,并返回相应的数据。
对于 GET /myplugin/v1/books/<id>
路由,我们使用了 args
参数来定义 id
参数的验证和清理规则。 validate_callback
用于验证参数的值是否有效,sanitize_callback
用于清理参数的值。
对于 POST /myplugin/v1/books
路由,我们也使用了 args
参数来定义 title
和 author
参数。 required
设置为 true
表示 title
参数是必须的。 type
用于指定参数的类型,description
用于描述参数的含义。 同时,使用了权限验证函数 current_user_can( 'publish_posts' )
只有拥有 publish_posts
权限的用户才能创建书籍。
七、总结
register_rest_route()
函数是 WordPress REST API 的核心函数之一。 它负责将我们定义的 REST API 路由注册到 WP_REST_Server
类的 $endpoints
属性中。 通过深入理解 register_rest_route()
函数的源码,我们可以更好地掌握 WordPress REST API 的工作原理,从而开发出更加强大和灵活的 API 应用。
函数/方法 | 作用 |
---|---|
register_rest_route() |
注册 REST API 路由到 WordPress。 |
WP_REST_Server 类 |
处理所有的 API 请求,包括路由匹配、权限验证、数据处理等等。 |
$WP_REST_Server->endpoints |
存储所有已注册的 REST API 路由的关联数组。 |
WP_REST_Server::register_route() |
真正完成路由注册的工作。 验证参数、规范化路由和命名空间、生成路由正则表达式、规范化参数、存储路由。 |
WP_REST_Server::get_route_regex() |
将路由字符串转换成正则表达式。 |
WP_REST_Server::normalize_methods() |
将 HTTP 方法字符串转换成一个大写字母的数组。 |
WP_REST_Request 类 |
表示一个 REST API 请求。 包含了请求的所有信息,包括请求方法、请求参数、请求头等等。 |
rest_ensure_response() |
确保返回一个 WP_REST_Response 对象。 如果传入的参数不是 WP_REST_Response 对象,则将其转换成 WP_REST_Response 对象。 |
WP_Error 类 |
表示一个错误。 可以用于返回 API 请求的错误信息。 |
current_user_can() |
检查当前用户是否具有指定的权限。 |
add_action( 'rest_api_init', ... ) |
在 WordPress REST API 初始化时执行指定的回调函数。 这是注册 REST API 路由的常用方法。 |
get_posts() |
获取文章。 可以通过参数指定文章类型、文章状态等等。 |
get_post() |
获取指定 ID 的文章。 |
wp_insert_post() |
插入一篇文章。 |
update_post_meta() |
更新文章的自定义字段。 |
$request->get_param( '参数名' ) |
从 WP_REST_Request 对象中获取指定名称的参数的值。 |
'validate_callback' => 'is_numeric' |
参数验证回调函数,用于验证参数的值是否有效。 |
'sanitize_callback' => 'absint' |
参数清理回调函数,用于清理参数的值。 |
'required' => true |
指定参数是否是必须的。 |
'type' => 'string' |
指定参数的类型。 |
'description' => '...' |
描述参数的含义。 |
希望这次讲座能帮助大家更好地理解 WordPress REST API 的底层机制。 谢谢大家!