我们知道 WordPress 的函数在失败的时候,是不会抛出异常的,因为 WordPress 在 PHP 4 的时候就创建了,那时候 PHP 语法结构还没有 try
/catch
异常处理机制。
WP_Error 错误处理机制
取而代之,WordPress 在失败的时候,返回返回一个WP_Error对象,比如插入文章的函数 wp_insert_post
,如果文章标题,内容摘要都为空的时候,就会插入失败,会返回 WP_Error
对象。
WP_Error
对象错误对象和异常很类似,也有一个错误代码和错误信息,比如上面的错误,返回 WP_Error
对象的错误代码就是 empty_content
,错误信息是:内容,标题和摘要为空。
WordPress 还提供了 is_wp_error
函数,用于判断接受到数据是不是 WP_Error
对象,这样我们在写代码的时候,就需要自己判断返回值是不是 WP_Error
对象,然后进行额外处理,举个例子,WPJAM Basic 的快速复制扩展功能的代码:
function wpjam_duplicate_post($post_id){
// 获取旧文章信息,并插入新文章
$post_arr = get_post($post_id, ARRAY_A);
$new_post_id = wp_inssert_post($post_arr, $wp_error=true);
if(is_wp_error($new_post_id)){ // 如果失败,返回错误
return $new_post_id;
}
// 获取旧文章的分类信息,并将同样的分类信息设置到新的文章中
foreach(get_object_taxonomies($post_arr['post_type']) as $taxonomy){
$terms = wp_get_object_terms($post_id, $taxonomy, ['fields' => 'ids']);
$result = wp_set_object_terms($new_post_id, $terms, $taxonomy);
if(is_wp_error($result)){ // 如果失败,返回错误
return $result;
}
}
// 假如还有其他操作
$result = other_post_function($new_post_id, $args);
if(is_wp_error($result)){ // 如果失败,返回错误
return $result;
}
return $new_post_id; // 最后才返回复制成功的文章 ID
}
上面的代码我为了方便演示,做了一些简化,留下大致的骨架,可以看出快速复制文章有三个过程,注释里面已经写的非常清楚,下面简单说一下
- 获取旧文章信息,并插入新文章,如果
WP_Error
对象,则直接返回。 - 获取旧文章的分类信息,并将同样的分类信息设置到新的文章中,同样碰到
WP_Error
对象,则直接返回 - 最后假设还有其他操作,同样也要处理错误。
这样的代码给人感觉就是满屏的错误处理,非常难受。
使用 Try / Catch 异常处理机制
有没有办法优化我们的代码呢?可以把 WP_Error
对象转换成 PHP 异常继承类的对象,然后使用现代 PHP 的 Try / Catch 异常处理机制来优化。
首先创建用于处理 WP_Error
对象的异常处理类:
class WPJAM_Exception extends Exception{
private $wp_error = null;
public function __construct($message, $code=0, Throwable $previous=null){
if(is_wp_error($message)){
$this->wp_error = $message;
$message = $this->wp_error->get_error_message();
$code = $this->wp_error->get_error_code();
}else{
$this->wp_error = new WP_Error($code, $message);
}
parent::__construct($message, 0, $previous);
}
public function get_wp_error(){
return $this->wp_error;
}
}
创建一个高阶函数 wpjam_try
,自动将 WP_Error
对象转换成异常:
function wpjam_try($callback, ...$args){
try{
$result = call_user_func_array($callback, $args);
if(is_wp_error($result)){
throw new WPJAM_Exception($result);
}
return $result;
}catch(Exception $e){
throw $e;
}
}
最后我们就可以使用 wpjam_try
对上面复制文章这段代码进行改造了
function wpjam_duplicate_post($post_id){
try{
// 获取旧文章信息,并插入新文章
$post_arr = get_post($post_id, ARRAY_A);
$new_post_id = wpjam_try('wp_inssert_post', $post_arr, $wp_error=true);
// 获取旧文章的分类信息,并将同样的分类信息设置到新的文章中
foreach(get_object_taxonomies($post_arr['post_type']) as $taxonomy){
$terms = wp_get_object_terms($post_id, $taxonomy, ['fields' => 'ids']);
$result = wpjam_try('wp_set_object_terms', $new_post_id, $terms, $taxonomy);
}
// 假如还有其他操作
$result = wpjam_try('other_post_function', $new_post_id, $args);
return $new_post_id; // 最后才返回复制成功的文章 ID
}catch(WPJAM_Exception $e){
if($exception){
throw $e;
}else{
return $e->get_wp_error();
}
}
}
改造的过程分成三步:
- 把会返回
WP_Error
对象的函数,通过wpjam_try
调用。 - 去掉所有
is_wp_error
的判断,因为wpjam_try
会抛出异常。 - 将所有代码放到
try/catch
的结构中,最后只需要捕捉异常,再将异常转换成WP_Error
对象即可。
这样就可以在 WordPress 写代码的时候,避免满屏幕的错误处理,最后返回还是 WP_Error
对象,保证了对原来逻辑的兼容。