WordPress源码深度解析之:插件加载顺序:`plugins_loaded`和`init` `action`的底层执行差异。

好的,各位听众,欢迎来到今天的“WordPress源码深度解析”小课堂!我是你们今天的讲师,老码农一枚。今天咱们聊点有意思的,关于WordPress插件加载的两个关键actionplugins_loadedinit。 这俩哥们儿,初学者容易搞混,但它们在WordPress启动过程中扮演的角色可是大相径庭。

开场白:别被表象迷惑!

很多新手朋友觉得,插件加载嘛,不就是把插件里的代码执行一遍吗? 看起来好像是这样,但WordPress内部的处理机制远比我们想象的要复杂。 就像看魔术一样,台上光鲜亮丽,台下可都是精密的机关。 plugins_loadedinit 这两个action,就是这些“机关”中的关键齿轮。

第一幕:WordPress的启动大戏

要理解plugins_loadedinit 的区别,我们先要对WordPress的启动流程有个大致的了解。 想象一下,WordPress的启动过程就像一场盛大的演出,各个角色(文件、函数、插件)按照剧本依次登场。

  1. wp-config.php:奠定基石
    • 这是演出的总策划书,定义了数据库连接信息、调试模式等关键配置。
    • WordPress首先读取这个文件,建立与数据库的连接。
  2. wp-settings.php:加载核心文件
    • 这是演出的导演,负责加载核心文件、注册全局变量等。
    • 它会加载wp-load.php,然后依次加载各种核心函数和类。
  3. wp-load.php:启动引擎
    • 这是演出的总调度,定义了WordPress的根目录、编码方式等。
    • 它主要负责加载wp-config.php,为后续的加载做好准备。
  4. wp-includes/plugin.php:插件管理中心
    • 这是插件的登记处,负责激活、停用、加载插件。
    • 它定义了get_option('active_plugins')函数,获取已激活的插件列表。
  5. 开始执行主题和插件
    • 主题和插件开始“登台演出”
    • 主题的functions.php和插件的代码会被加载和执行

第二幕:plugins_loaded:插件的集体亮相

好,现在重点来了。 plugins_loaded 这个action,就发生在核心文件加载完毕,但在主题和插件完全启动之前。 它的主要作用是:让所有已激活的插件加载它们的核心代码。

  • 触发时机:wp-settings.php中,require( ABSPATH . WPINC . '/plugin.php' ); 之后,do_action( 'plugins_loaded' );被调用。
  • 作用: 插件可以在这个action中注册全局变量、定义常量、加载核心类等。
  • 特点: 这个action的触发时机非常早,此时WordPress的核心功能(比如用户认证、文章类型)可能还没有完全加载完毕。

代码示例:

<?php
/**
 * Plugin Name: My Awesome Plugin
 * Description: A plugin that does awesome things.
 */

add_action( 'plugins_loaded', 'my_awesome_plugin_loaded' );

function my_awesome_plugin_loaded() {
    // 定义常量
    define( 'MY_AWESOME_PLUGIN_VERSION', '1.0.0' );

    // 加载核心类
    require_once plugin_dir_path( __FILE__ ) . 'includes/class-my-awesome-plugin.php';

    // 创建插件实例
    $my_plugin = new My_Awesome_Plugin();
}

注意事项:

  • 由于plugins_loaded触发时机较早,因此不要在这个action中依赖WordPress的核心功能。 比如,不要尝试获取当前用户的信息,因为用户认证可能还没有完成。
  • 尽量在这个action中完成插件的基础设置,为后续的操作做好准备。

第三幕:init:万事俱备,只欠东风

init 这个action,发生在WordPress核心功能加载完毕之后,但在页面渲染之前。 它的主要作用是:让插件和主题执行更复杂的操作,比如注册文章类型、添加自定义字段、处理用户请求等。

  • 触发时机:wp()函数中,do_action( 'init' );被调用。
  • 作用: 插件和主题可以在这个action中执行各种操作,比如注册文章类型、添加自定义字段、处理用户请求等。
  • 特点: 这个action的触发时机相对较晚,此时WordPress的核心功能已经完全加载完毕。

代码示例:

<?php
/**
 * Plugin Name: My Awesome Plugin
 * Description: A plugin that does awesome things.
 */

add_action( 'init', 'my_awesome_plugin_init' );

function my_awesome_plugin_init() {
    // 注册自定义文章类型
    register_post_type( 'my_awesome_post', array(
        'labels' => array(
            'name' => __( 'Awesome Posts', 'my-awesome-plugin' ),
            'singular_name' => __( 'Awesome Post', 'my-awesome-plugin' ),
        ),
        'public' => true,
        'has_archive' => true,
        'supports' => array( 'title', 'editor', 'thumbnail' ),
    ) );

    // 添加自定义字段
    add_meta_box(
        'my_awesome_meta_box',
        __( 'Awesome Meta Box', 'my-awesome-plugin' ),
        'my_awesome_meta_box_callback',
        'my_awesome_post',
        'normal',
        'default'
    );
}

function my_awesome_meta_box_callback( $post ) {
    // 显示自定义字段
    echo '<label for="my_awesome_field">' . __( 'Awesome Field:', 'my-awesome-plugin' ) . '</label>';
    echo '<input type="text" id="my_awesome_field" name="my_awesome_field" value="' . esc_attr( get_post_meta( $post->ID, 'my_awesome_field', true ) ) . '" />';
}

注意事项:

  • 由于init触发时机较晚,因此可以在这个action中安全地依赖WordPress的核心功能。
  • 尽量在这个action中完成插件的业务逻辑处理。

第四幕:源码分析:追根溯源

光说理论还不够,我们来扒一扒WordPress的源码,看看plugins_loadedinit 到底是怎么被触发的。

plugins_loaded 的触发:

打开wp-settings.php,你会发现:

<?php
// Loads the WordPress environment
if ( !isset( $wp_did_header ) ) {

    $wp_did_header = true;

    require_once( dirname(__FILE__) . '/wp-load.php' );

    wp();

    require_once( ABSPATH . WPINC . '/template-loader.php' );

}

再打开wp-load.php,你会发现

<?php

/**
 * Bootstrap file for loading the WordPress environment.
 *
 * @package WordPress
 */

if ( ! isset( $table_prefix ) )
    $table_prefix = 'wp_';

define('WPINC', 'wp-includes');

require( dirname(__FILE__) . '/wp-config.php' );

require_once( ABSPATH . 'wp-includes/load.php' );

require_once( ABSPATH . 'wp-includes/default-constants.php' );

require_once( ABSPATH . 'wp-includes/plugin.php' );

// Sets up the WordPress vars and included files.
require_once( ABSPATH . 'wp-settings.php' );

关键的代码是require_once( ABSPATH . WPINC . '/plugin.php' );之后,在wp-settings.php中,你会看到:

do_action( 'plugins_loaded' );

这行代码就是触发plugins_loaded 的关键! 它告诉WordPress: “嘿,所有插件,该加载你们的核心代码了!”

init 的触发:

init 的触发稍微复杂一点,它藏在wp()函数里面。 wp()函数位于wp-includes/functions.php文件中。 打开这个文件,找到wp()函数,你会看到:

function wp() {
    global $wp, $wp_query, $wp_the_query, $wp_rewrite, $wp_did_header;

    $wp->main();

    if ( ! isset( $wp_did_header ) ) {
        $wp_did_header = true;
        $wp->send_headers();
    }

    $wp_query->init();
    $wp_the_query->init();

    /**
     * Fires after WordPress has finished loading but before any headers are sent.
     *
     * @since 1.5.0
     */
    do_action( 'init' );

    /**
     * Fires before the HTTP headers are sent to the browser.
     *
     * @since 2.1.0
     */
    do_action( 'wp_loaded' );
}

看到do_action( 'init' );了吗? 这就是init 被触发的地方! 它告诉WordPress:“嘿,所有插件和主题,核心功能已经加载完毕,你们可以开始执行更复杂的操作了!”

第五幕:表格对比:一目了然

为了让大家更清晰地理解plugins_loadedinit 的区别,我们用一个表格来总结一下:

特性 plugins_loaded init
触发时机 核心文件加载完毕,但在主题和插件完全启动之前 WordPress核心功能加载完毕之后,但在页面渲染之前
主要作用 加载插件的核心代码 执行更复杂的操作,比如注册文章类型、添加自定义字段
依赖核心功能 尽量避免依赖 可以安全地依赖
典型应用 定义常量、加载核心类、注册全局变量 注册文章类型、添加自定义字段、处理用户请求
源码位置 wp-settings.php wp-includes/functions.php 中的 wp() 函数
触发时机相对顺序 更早 更晚

第六幕:实战演练:避免踩坑

了解了plugins_loadedinit 的区别,我们再来看几个实际的例子,避免大家在开发过程中踩坑。

坑1:在plugins_loaded 中获取当前用户信息

<?php
add_action( 'plugins_loaded', 'my_plugin_get_current_user' );

function my_plugin_get_current_user() {
    $current_user = wp_get_current_user();

    if ( $current_user->exists() ) {
        // Do something with the current user
        echo 'Hello, ' . $current_user->display_name . '!';
    } else {
        echo 'Not logged in.';
    }
}

这段代码看似没问题,但实际上在plugins_loaded 触发时,用户认证可能还没有完成,wp_get_current_user() 可能返回一个空对象。 正确的做法是将这段代码放在init action中:

<?php
add_action( 'init', 'my_plugin_get_current_user' );

function my_plugin_get_current_user() {
    $current_user = wp_get_current_user();

    if ( $current_user->exists() ) {
        // Do something with the current user
        echo 'Hello, ' . $current_user->display_name . '!';
    } else {
        echo 'Not logged in.';
    }
}

坑2:在init 中定义常量

虽然在init 中可以安全地依赖WordPress的核心功能,但不建议在这个action中定义常量。 因为常量通常需要在代码的最开始就定义,以便在后续的代码中使用。 如果将常量定义放在init 中,可能会导致一些代码无法访问到这些常量。 正确的做法是将常量定义放在plugins_loaded action中。

总结:各司其职,高效协作

plugins_loadedinit 这两个action,就像一个团队里的两个重要成员,各司其职,高效协作,共同完成了WordPress的启动过程。 plugins_loaded 负责打基础,加载核心代码,init 负责执行业务逻辑,处理用户请求。 只有充分理解了它们的作用和特点,才能在WordPress开发中游刃有余,写出高质量的插件和主题。

课后作业:

  1. 编写一个插件,分别在plugins_loadedinit action中输出一段文字,观察它们的执行顺序。
  2. 尝试在plugins_loaded 中注册一个自定义文章类型,看看会发生什么。

好啦,今天的课程就到这里。希望大家能够学有所获,在WordPress开发的道路上越走越远! 咱们下次再见!

发表回复

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