解析 WordPress `_doing_it_wrong()` 函数的源码:如何在开发过程中发现和报告潜在的错误。

好啦好啦,各位观众老爷们,今天咱们来聊聊 WordPress 里一个“不太光彩”的函数:_doing_it_wrong()。 别看它名字挺长,其实它就是 WordPress 偷偷摸摸告诉你:“嘿,哥们儿,你这代码写得不太对劲啊!” 的小喇叭。

开场白: 咱们为什么要关心 _doing_it_wrong()

想象一下,你辛辛苦苦写了个插件,结果安装到别人的网站上,稀里糊涂地报错了。用户跑来跟你说:“你的插件不行啊!”,你一脸懵逼,调试半天也找不到问题。

如果你的插件里用了错误的 WordPress 函数或者参数,_doing_it_wrong() 可能会提前告诉你,让你避免这种尴尬的局面。 它可以帮你发现一些潜在的错误,提高代码质量,让你的插件或者主题更加健壮。

第一幕:_doing_it_wrong() 的真面目

咱们先来看看 _doing_it_wrong() 的庐山真面目(源码在 wp-includes/functions.php 里):

function _doing_it_wrong( $function, $message, $version ) {
    /**
     * Fires when a function is being called incorrectly.
     *
     * @since 3.1.0
     *
     * @param string $function The function that was called.
     * @param string $message  A message explaining what went wrong.
     * @param string $version  The version of WordPress where the message was added.
     */
    do_action( 'doing_it_wrong_run', $function, $message, $version );
    $doing_it_wrong = WP_DEBUG && apply_filters( 'doing_it_wrong_trigger_error', true );

    if ( $doing_it_wrong ) {
        $message = sprintf( '%1$s was called <strong>incorrectly</strong>. %2$s. Please see <a href="%3$s" rel="noopener noreferrer" target="_blank">Debugging in WordPress</a> for more information.',
            '<code>' . $function . '()</code>',
            $message,
            __( 'https://wordpress.org/support/article/debugging-in-wordpress/' )
        );

        if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) {
            header( 'X-WP-Doing-It-Wrong: ' . $function . ' ' . $message, false );
        }

        if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) {
            header( 'X-WP-Doing-It-Wrong: ' . $function . ' ' . $message, false );
        }

        trigger_error( $message, E_USER_NOTICE );
    }
}

别怕,代码不多。简单来说,它做了这几件事:

  1. 触发 doing_it_wrong_run action: 允许其他插件或者主题监听这个事件,做一些自定义的操作,比如记录日志。
  2. 判断是否需要触发错误: 只有在 WP_DEBUGtrue,并且 doing_it_wrong_trigger_error 过滤器返回 true 时,才会真正触发错误。
  3. 格式化错误信息: 将函数名、错误信息和 WordPress 调试文档的链接组合成一个友好的错误提示。
  4. 设置 HTTP header (如果是在 AJAX 或 REST 请求中): 方便开发者在浏览器开发者工具中查看错误信息。
  5. 触发 E_USER_NOTICE 级别的错误: 这个错误会被 PHP 记录下来,并且可能会显示在 WordPress 的调试日志中。

参数说明:

参数 类型 描述
$function string 错误函数的名字 (例如:'the_content' )
$message string 描述哪里出错了 (例如:'参数 $more_link_text 应该是一个字符串' )
$version string 引入此错误提示的 WordPress 版本 (例如:'3.0' )

第二幕:谁在用 _doing_it_wrong()

WordPress 核心代码里有很多地方都用到了 _doing_it_wrong()。 咱们来看看几个例子:

  • get_currentuserinfo() (已弃用):

    function get_currentuserinfo() {
        _doing_it_wrong( __FUNCTION__, __( '<code>get_currentuserinfo</code> is deprecated since version 4.5! Use <code>wp_get_current_user()</code> instead.' ), '4.5.0' );
        wp_get_current_user();
    }

    这个函数在 WordPress 4.5 之后被标记为已弃用,建议使用 wp_get_current_user() 代替。 如果你还在用 get_currentuserinfo(),就会触发 _doing_it_wrong()

  • automatic_feed_links() (参数错误):

    function automatic_feed_links( $deprecated = false ) {
        if ( $deprecated ) {
            _doing_it_wrong( __FUNCTION__, __( 'The <code>$deprecated</code> argument is deprecated since version 3.0.1. Use <code>add_theme_support( 'automatic-feed-links' );</code> instead.' ), '3.0.1' );
        }
    
        add_theme_support( 'automatic-feed-links' );
    }

    如果你给 automatic_feed_links() 传递了任何参数,就会触发 _doing_it_wrong(),因为它已经不再接受任何参数了。

这些例子告诉我们,_doing_it_wrong() 主要用于:

  • 标记已弃用的函数。
  • 提示函数的使用方式不正确。
  • 提示函数存在潜在的性能问题或者安全风险。

第三幕:如何在开发中利用 _doing_it_wrong()

作为一名负责任的开发者,我们应该在自己的代码里也使用 _doing_it_wrong(),特别是当我们:

  • 弃用某个函数时。
  • 更改某个函数的参数时。
  • 发现某个函数的使用方式存在潜在的问题时。

下面是一些使用 _doing_it_wrong() 的例子:

  • 自定义函数弃用:

    function my_old_function( $param1, $param2 ) {
        _doing_it_wrong( 'my_old_function', '这个函数已经过时,请使用 my_new_function 代替。', '1.0.0' );
        return my_new_function( $param1, $param2 );
    }
    
    function my_new_function( $param1, $param2 ) {
        // 新函数的逻辑
        return $param1 + $param2;
    }

    这样,当有人调用 my_old_function() 时,就会收到一个错误提示,告诉他们应该使用 my_new_function()

  • 参数验证:

    function my_plugin_setting( $setting_name, $default_value = '' ) {
        if ( ! is_string( $setting_name ) ) {
            _doing_it_wrong( 'my_plugin_setting', '参数 $setting_name 必须是一个字符串。', '1.0.0' );
            return $default_value;
        }
    
        // 获取设置的逻辑
        // ...
    }

    这个例子展示了如何使用 _doing_it_wrong() 来验证函数的参数类型。

  • 不推荐的使用方式:

    function my_potentially_slow_function( $data ) {
        if ( count( $data ) > 100 ) {
            _doing_it_wrong( 'my_potentially_slow_function', '当 $data 数组元素超过 100 个时,这个函数可能会很慢。请考虑使用分页或者其他优化方式。', '1.0.0' );
        }
    
        // 函数的逻辑
        // ...
    }

    这个例子展示了如何提示用户某个函数在特定情况下可能会存在性能问题。

第四幕:如何查看 _doing_it_wrong() 的错误信息?

要查看 _doing_it_wrong() 产生的错误信息,你需要:

  1. 开启 WP_DEBUGwp-config.php 文件中,将 WP_DEBUG 设置为 true

    define( 'WP_DEBUG', true );
  2. 开启 WP_DEBUG_LOG (可选): 如果你想将错误信息记录到日志文件中,可以将 WP_DEBUG_LOG 设置为 true

    define( 'WP_DEBUG_LOG', true );

    错误日志文件会保存在 wp-content/debug.log

  3. 开启 WP_DEBUG_DISPLAY (可选): 如果你想在页面上显示错误信息,可以将 WP_DEBUG_DISPLAY 设置为 true

    define( 'WP_DEBUG_DISPLAY', true );

    注意: 在生产环境中,强烈建议不要开启 WP_DEBUG_DISPLAY,因为它可能会暴露敏感信息。

  4. 查看错误信息:

    • 页面显示: 如果你开启了 WP_DEBUG_DISPLAY,错误信息会直接显示在页面上。
    • 错误日志: 如果你开启了 WP_DEBUG_LOG,错误信息会记录在 wp-content/debug.log 文件中。
    • 浏览器开发者工具: 如果错误发生在 AJAX 或者 REST 请求中,错误信息会出现在响应的 HTTP header 中 ( X-WP-Doing-It-Wrong )。

第五幕:doing_it_wrong_run Action 的妙用

前面提到,_doing_it_wrong() 会触发一个名为 doing_it_wrong_run 的 action。 我们可以利用这个 action 来做一些自定义的操作,比如:

  • 记录错误日志到数据库。
  • 发送邮件通知开发者。
  • 在后台管理界面显示错误信息。

下面是一个使用 doing_it_wrong_run action 的例子:

add_action( 'doing_it_wrong_run', 'my_custom_doing_it_wrong_handler', 10, 3 );

function my_custom_doing_it_wrong_handler( $function, $message, $version ) {
    // 记录错误日志到数据库
    global $wpdb;
    $wpdb->insert(
        'my_error_log',
        array(
            'function' => $function,
            'message'  => $message,
            'version'  => $version,
            'timestamp' => current_time( 'mysql' ),
        )
    );

    // 发送邮件通知开发者
    $to      = '[email protected]';
    $subject = 'WordPress Doing It Wrong Error';
    $body    = "函数: {$function}n错误信息: {$message}nWordPress 版本: {$version}";
    $headers = array( 'Content-Type: text/plain; charset=UTF-8' );

    wp_mail( $to, $subject, $body, $headers );
}

这个例子展示了如何使用 doing_it_wrong_run action 将错误信息记录到数据库,并发送邮件通知开发者。

第六幕:doing_it_wrong_trigger_error Filter 的灵活运用

_doing_it_wrong() 函数在触发错误之前,会应用一个名为 doing_it_wrong_trigger_error 的 filter。 我们可以利用这个 filter 来控制是否要触发错误。 比如,我们可以根据函数名、错误信息或者 WordPress 版本来决定是否触发错误。

下面是一个使用 doing_it_wrong_trigger_error filter 的例子:

add_filter( 'doing_it_wrong_trigger_error', 'my_custom_doing_it_wrong_filter', 10, 1 );

function my_custom_doing_it_wrong_filter( $trigger_error ) {
    global $doing_it_wrong_function; // 获取触发 _doing_it_wrong 的函数名

    // 如果是某个特定的函数,则不触发错误
    if ( $doing_it_wrong_function === 'my_old_function' ) {
        return false;
    }

    return $trigger_error;
}

重要提示: 在 WordPress 5.5 之后,可以通过 debug_backtrace() 获取触发 _doing_it_wrong() 的函数名,而不是依赖全局变量。 上面的例子可以修改为:

add_filter( 'doing_it_wrong_trigger_error', 'my_custom_doing_it_wrong_filter', 10, 1 );

function my_custom_doing_it_wrong_filter( $trigger_error ) {
  $backtrace = debug_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS, 2 );

  if ( isset( $backtrace[1]['function'] ) && $backtrace[1]['function'] === 'my_old_function' ) {
    return false;
  }

  return $trigger_error;
}

这个例子展示了如何使用 doing_it_wrong_trigger_error filter 阻止某个特定函数的 _doing_it_wrong() 错误提示。 请谨慎使用这个 filter,因为它可能会隐藏一些重要的错误信息。

第七幕:最佳实践和注意事项

  • 只在开发环境中使用 _doing_it_wrong() 在生产环境中,你应该关闭 WP_DEBUG,避免暴露敏感信息。
  • 提供清晰的错误信息。 错误信息应该足够详细,能够帮助开发者快速找到问题所在。
  • 避免过度使用 _doing_it_wrong() 只在真正需要的时候才使用它。
  • 及时修复 _doing_it_wrong() 提示的错误。 不要忽略这些提示,它们可能预示着潜在的问题。
  • 仔细阅读 WordPress 官方文档。 了解每个函数的使用方式和注意事项,避免犯低级错误。
  • 关注 WordPress 的更新日志。 了解哪些函数被弃用了,哪些函数的使用方式发生了变化。

总结:_doing_it_wrong() 是你的好帮手

_doing_it_wrong() 是一个非常有用的函数,它可以帮助我们发现代码中的潜在错误,提高代码质量。 只要我们正确地使用它,它就能成为我们开发 WordPress 插件和主题的好帮手。

好了,今天的讲座就到这里。 希望大家以后写代码的时候,多想想 _doing_it_wrong(),少踩一些坑,写出更棒的 WordPress 代码! 散会!

发表回复

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