WordPress源码深度解析之:`wp-includes/wp-db.php`:`$wpdb`类的数据库连接与`SQL`查询的底层封装。

各位观众老爷,晚上好!今天咱来聊聊WordPress的骨骼——wp-db.php,特别是其中那个神秘又强大的$wpdb类。说它是骨骼,是因为WordPress几乎所有的数据操作都得通过它,没有它,WordPress就成了一堆漂亮的HTML代码,啥也干不了。

咱们今天的主题是$wpdb类的数据库连接与SQL查询的底层封装。准备好了吗?咱们开始!

一、 $wpdb:你的专属数据库管家

$wpdb类,简单来说,就是WordPress为了方便开发者操作数据库而设计的一个类。它封装了数据库连接、查询、错误处理等等功能,让咱们可以不用直接跟MySQL打交道,而是通过它来完成各种数据库操作。

想象一下,如果没有$wpdb,每次想查个文章标题,都得自己写一堆mysql_connectmysql_query之类的代码,那得多麻烦啊!有了$wpdb,咱们只需要调用几个简单的函数,就能轻松搞定。

二、 连接数据库:建立友谊的第一步

$wpdb类最重要的功能之一就是建立数据库连接。WordPress在初始化的时候,会创建一个$wpdb对象,并使用wp-config.php中定义的数据库连接信息(DB_NAME, DB_USER, DB_PASSWORD, DB_HOST)来连接数据库。

先来看看$wpdb类中几个关键的属性:

属性名 类型 描述
dbuser string 数据库用户名
dbpassword string 数据库密码
dbname string 数据库名称
dbhost string 数据库主机地址
dbh resource 数据库连接资源句柄 (通常是mysqli对象)
query string 最后一次执行的SQL查询语句
last_result array/object 最后一次查询的结果集 (二维数组或对象数组,取决于查询方式)
num_rows int 最后一次查询影响的行数
last_error string 最后一次查询的错误信息
prefix string 表前缀 (在wp-config.php中定义,用于区分不同的WordPress安装)
queries array 所有执行过的SQL查询语句的数组 (用于调试)
query_times array 每条SQL查询语句的执行时间数组 (用于性能分析)
use_mysqli bool 是否使用mysqli扩展
is_mysql bool 是否是MySQL数据库

连接数据库的关键代码(简化版,实际代码更复杂):

class wpdb {

    public $dbuser;
    public $dbpassword;
    public $dbname;
    public $dbhost;
    public $dbh; // Database handle
    public $query;
    public $last_result;
    public $num_rows;
    public $last_error;
    public $prefix;
    public $queries = array();
    public $query_times = array();
    public $use_mysqli = true;
    public $is_mysql = true;

    function __construct( $dbuser, $dbpassword, $dbname, $dbhost ) {
        $this->dbuser = $dbuser;
        $this->dbpassword = $dbpassword;
        $this->dbname = $dbname;
        $this->dbhost = $dbhost;

        $this->db_connect();
    }

    function db_connect() {
        // Use mysqli if loaded
        if ( function_exists( 'mysqli_connect' ) ) {
            $this->use_mysqli = true;
            $this->is_mysql = true;
            $this->dbh = mysqli_connect( $this->dbhost, $this->dbuser, $this->dbpassword, $this->dbname );

            if ( !$this->dbh ) {
                $this->bail( mysqli_connect_error() );
            }
        } else {
            // Fallback to mysql (deprecated)
            $this->use_mysqli = false;
            $this->is_mysql = true;
            $this->dbh = mysql_connect( $this->dbhost, $this->dbuser, $this->dbpassword );

            if ( !$this->dbh ) {
                $this->bail( mysql_error() );
            }

            if ( !mysql_select_db( $this->dbname, $this->dbh ) ) {
                $this->bail( mysql_error() );
            }
        }

        return $this->dbh;
    }

    function bail( $message ) {
        echo '<h1>Database Error</h1>';
        echo '<p>' . $message . '</p>';
        die();
    }
}

// 在WordPress初始化时,会创建 $wpdb 对象
global $wpdb;
$wpdb = new wpdb( DB_USER, DB_PASSWORD, DB_NAME, DB_HOST );

这段代码的核心在于db_connect()函数。它首先会检查是否安装了mysqli扩展,如果安装了,就使用mysqli_connect()函数来连接数据库;如果没有安装,就使用mysql_connect()函数(注意:mysql_connect函数已经过时,不建议使用)。如果连接失败,就调用bail()函数来显示错误信息并终止程序。

三、 执行SQL查询:让数据为你跳舞

连接数据库之后,咱们就可以执行各种SQL查询了。$wpdb类提供了多个函数来执行SQL查询,最常用的有:

  • query():执行任意SQL查询语句,返回受影响的行数。
  • get_results():执行SQL查询语句,返回结果集(数组或对象)。
  • get_row():执行SQL查询语句,返回结果集中的第一行(对象)。
  • get_col():执行SQL查询语句,返回结果集中的第一列(数组)。
  • get_var():执行SQL查询语句,返回结果集中的第一个字段的值。
  • insert():插入数据到数据库表。
  • update():更新数据库表中的数据。
  • delete():删除数据库表中的数据。

咱们一个一个来看:

  1. query():一锤定音

    query()函数是最基础的查询函数,它可以执行任意SQL查询语句。它返回受影响的行数,如果查询失败,则返回false

    function query( $query ) {
        global $EZSQL_ERROR;
        $this->func_call = "$db->query("$query")";
    
        // For reg expressions.
        $query = trim($query);
    
        // Log how the function was called.
        $this->call_dump[] = $this->func_call;
    
        // Keep track of the last query.
        $this->last_query = $query;
    
        // Reset error number.
        $this->last_error = '';
    
        if ( defined('SAVEQUERIES') && SAVEQUERIES ) {
            $this->timer_start();
        }
    
        // Perform the query via mysqli or mysql functions
        if ( $this->use_mysqli ) {
            $result = mysqli_query( $this->dbh, $query );
        } else {
            $result = mysql_query( $query, $this->dbh );
        }
    
        if ( defined('SAVEQUERIES') && SAVEQUERIES ) {
            $this->timer_stop();
            $this->queries[] = $query;
            $this->query_times[] = $this->timer_elapsed;
        }
    
        // If there is an error then take note of it..
        if ( $this->use_mysqli ) {
            if ( mysqli_error( $this->dbh ) ) {
                $this->last_error = mysqli_error( $this->dbh );
                $this->print_error();
                return false;
            }
        } else {
            if ( mysql_error( $this->dbh ) ) {
                $this->last_error = mysql_error( $this->dbh );
                $this->print_error();
                return false;
            }
        }
    
        // Query was an insert, delete, update, replace
        if ( preg_match("/^(insert|delete|update|replace)s+/i", $query) ) {
            $this->num_rows = ( $this->use_mysqli ) ? mysqli_affected_rows( $this->dbh ) : mysql_affected_rows( $this->dbh );
            return $this->num_rows;
        }
    
        // Query was a select
        if ( $result ) {
            if ( $this->use_mysqli ) {
                $num_rows = mysqli_num_rows( $result );
            } else {
                $num_rows = mysql_num_rows( $result );
            }
            return $num_rows; // 返回查询结果的行数
        }
    
        return false; // 查询失败
    }

    例如:

    global $wpdb;
    $sql = "UPDATE {$wpdb->prefix}posts SET post_title = 'Hello World' WHERE ID = 1";
    $result = $wpdb->query( $sql );
    
    if ( $result !== false ) {
        echo "更新成功,影响了{$result}行";
    } else {
        echo "更新失败,错误信息:{$wpdb->last_error}";
    }

    注意,{$wpdb->prefix}是WordPress表前缀,用于避免表名冲突。

  2. get_results():满载而归

    get_results()函数执行SQL查询语句,并返回结果集。它可以返回数组或对象,取决于你传递的参数。

    function get_results( $query = null, $output = OBJECT ) {
        global $EZSQL_ERROR;
        $return_val = array();
    
        $this->func_call = "$db->get_results("$query, $output")";
    
        // If there is a query then perform it if not then try to use last query.
        if ( $query ) {
            $this->query($query);
        }
    
        if ( $this->last_result ) {
            $i = 0;
    
            while ( $row = ( $this->use_mysqli ) ? mysqli_fetch_object( $this->result ) : mysql_fetch_object( $this->result ) ) {
                if ( $output == OBJECT ) {
                    // Return an array of objects
                    $return_val[$i] = $row;
                } elseif ( $output == ARRAY_A ) {
                    // Return an array of associative arrays
                    $return_val[$i] = (array) $row;
                } elseif ( $output == ARRAY_N ) {
                    // Return an array of numbered arrays
                    $return_val[$i] = array_values( (array) $row );
                } else {
                    $this->print_error(" $db->get_results(string query, output type); Output type must be one of: OBJECT, ARRAY_A, ARRAY_N");
                }
    
                $i++;
            }
    
            if ( $i ) {
                $this->num_rows = $i;
                mysqli_free_result( $this->result ); // release memory
                return $return_val;
            } else {
                return null;
            }
    
        } else {
            return null;
        }
    
    }

    $output参数可以取以下值:

    • OBJECT(默认值):返回对象数组。
    • ARRAY_A:返回关联数组数组。
    • ARRAY_N:返回索引数组数组。

    例如:

    global $wpdb;
    $sql = "SELECT ID, post_title FROM {$wpdb->prefix}posts WHERE post_status = 'publish' LIMIT 10";
    $posts = $wpdb->get_results( $sql );
    
    if ( $posts ) {
        foreach ( $posts as $post ) {
            echo "{$post->ID}: {$post->post_title}<br>";
        }
    } else {
        echo "没有找到文章";
    }

    如果 $output 设置为 ARRAY_A,那么访问文章标题的方式就变成了 $post['post_title']

  3. get_row():独占鳌头

    get_row()函数执行SQL查询语句,并返回结果集中的第一行。它也可以返回对象、关联数组或索引数组,取决于你传递的参数。

    function get_row( $query = null, $output = OBJECT, $y = 0 ) {
        global $EZSQL_ERROR;
        $return_val = null;
    
        $this->func_call = "$db->get_row("$query, $output, $y")";
    
        // If there is a query then perform it if not then try to use last query.
        if ( $query ) {
            $this->query($query);
        }
    
        if ( $this->last_result ) {
            if ( $output == OBJECT ) {
                // Return an object
                return @( $this->use_mysqli ) ? mysqli_fetch_object( $this->result ) : mysql_fetch_object( $this->result );
            } elseif ( $output == ARRAY_A ) {
                // Return an associative array
                return @( $this->use_mysqli ) ? mysqli_fetch_assoc( $this->result ) : mysql_fetch_assoc( $this->result );
            } elseif ( $output == ARRAY_N ) {
                // Return an numbered array
                return @( $this->use_mysqli ) ? mysqli_fetch_row( $this->result ) : mysql_fetch_row( $this->result );
            } else {
                $this->print_error(" $db->get_row(string query, output type, int offset); Output type must be one of: OBJECT, ARRAY_A, ARRAY_N");
            }
        }
        return $return_val;
    }

    $output参数的含义与get_results()函数相同。$y参数表示要返回的行号(从0开始),默认为0,即返回第一行。

    例如:

    global $wpdb;
    $sql = "SELECT ID, post_title FROM {$wpdb->prefix}posts WHERE post_status = 'publish' ORDER BY ID DESC LIMIT 1";
    $post = $wpdb->get_row( $sql );
    
    if ( $post ) {
        echo "最新文章的ID是:{$post->ID},标题是:{$post->post_title}";
    } else {
        echo "没有找到文章";
    }
  4. get_col():提取精华

    get_col()函数执行SQL查询语句,并返回结果集中的第一列。它返回一个索引数组。

    function get_col( $query = null, $x = 0 ) {
        global $EZSQL_ERROR;
        $return_val = array();
    
        $this->func_call = "$db->get_col("$query, $x")";
    
        // If there is a query then perform it if not then try to use last query.
        if ( $query ) {
            $this->query($query);
        }
    
        if ( $this->last_result ) {
            $i = 0;
    
            while ( $row = ( $this->use_mysqli ) ? mysqli_fetch_row( $this->result ) : mysql_fetch_row( $this->result ) ) {
                $return_val[$i] = $row[$x];
                $i++;
            }
        }
    
        if ( $return_val ) {
            mysqli_free_result( $this->result ); // release memory
            return $return_val;
        } else {
            return null;
        }
    }

    $x参数表示要返回的列号(从0开始),默认为0,即返回第一列。

    例如:

    global $wpdb;
    $sql = "SELECT post_title FROM {$wpdb->prefix}posts WHERE post_status = 'publish' LIMIT 5";
    $titles = $wpdb->get_col( $sql );
    
    if ( $titles ) {
        foreach ( $titles as $title ) {
            echo $title . "<br>";
        }
    } else {
        echo "没有找到文章标题";
    }
  5. get_var():一击命中

    get_var()函数执行SQL查询语句,并返回结果集中的第一个字段的值。

    function get_var( $query = null, $x = 0, $y = 0 ) {
        global $EZSQL_ERROR;
        $this->func_call = "$db->get_var("$query, $x, $y")";
    
        // If there is a query then perform it if not then try to use last query.
        if ( $query ) {
            $this->query($query);
        }
    
        if ( $this->last_result ) {
            $row = ( $this->use_mysqli ) ? mysqli_fetch_row( $this->result ) : mysql_fetch_row( $this->result );
    
            if( isset( $row[$x] ) ) {
                mysqli_free_result( $this->result ); // release memory
                return $row[$x];
            } else {
                return null;
            }
        } else {
            return null;
        }
    }

    $x参数表示要返回的列号(从0开始),默认为0,即返回第一列。$y参数表示要返回的行号(从0开始),默认为0,即返回第一行。

    例如:

    global $wpdb;
    $sql = "SELECT COUNT(*) FROM {$wpdb->prefix}posts WHERE post_status = 'publish'";
    $count = $wpdb->get_var( $sql );
    
    echo "共有{$count}篇已发布的文章";
  6. insert()update()delete():增删改查

    这三个函数分别用于插入、更新和删除数据。它们都接受一个表名和一个包含数据的数组作为参数。

    function insert( $table, $data, $format = null ) {
        return $this->replace( $table, $data, $format, 'INSERT' );
    }
    
    function update( $table, $data, $where, $format = null, $where_format = null ) {
        return $this->replace( $table, $data, $where, 'UPDATE', $format, $where_format );
    }
    
    function delete( $table, $where, $where_format = null ) {
        $q = "DELETE FROM `$table`";
        $q .= ' WHERE ' . $this->prepare_where( $where, $where_format );
    
        return $this->query( $q );
    }

    例如:

    global $wpdb;
    
    // 插入数据
    $data = array(
        'post_title' => 'New Post',
        'post_content' => 'This is the content of the new post.',
        'post_status' => 'draft',
        'post_date' => current_time( 'mysql' )
    );
    $wpdb->insert( $wpdb->prefix . 'posts', $data );
    $new_post_id = $wpdb->insert_id; // 获取插入的ID
    
    // 更新数据
    $data = array(
        'post_title' => 'Updated Post Title'
    );
    $where = array(
        'ID' => $new_post_id
    );
    $wpdb->update( $wpdb->prefix . 'posts', $data, $where );
    
    // 删除数据
    $where = array(
        'ID' => $new_post_id
    );
    $wpdb->delete( $wpdb->prefix . 'posts', $where );

    注意,$wpdb->insert_id可以获取最后一次插入操作的ID。

四、 prepare():安全第一

在使用$wpdb进行数据库操作时,一定要注意SQL注入的风险。为了避免SQL注入,可以使用$wpdb->prepare()函数来对SQL语句进行预处理。

function prepare( $query, ...$args ) {
    if ( is_null( $query ) ) {
        return;
    }

    $args = func_get_args();
    array_shift( $args ); // 将 $query 从参数列表中移除
    if ( is_array( $args[0] ) ) {
        $args = $args[0];
    }
    $query = str_replace( "'%s'", '%s', $query ); // strip single quotes
    $query = str_replace( '"%s"', '%s', $query ); // strip double quotes
    $query = str_replace( "'%d'", '%d', $query ); // strip single quotes
    $query = str_replace( '"%d"', '%d', $query ); // strip double quotes
    $query = str_replace( '%s', "'%s'", $query ); // quote the strings, avoiding escaped strings like '%s'
    $query = str_replace( '%d', "'%d'", $query ); // quote the digits, avoiding escaped strings like '%d'

    $args = array_map( array( $this, 'esc_like' ), $args );
    return vsprintf( $query, array_map( array( $this, '_real_escape' ), $args ) );
}

prepare()函数的用法是:

global $wpdb;
$sql = $wpdb->prepare(
    "SELECT * FROM {$wpdb->prefix}posts WHERE post_title = %s AND post_status = %s",
    'Hello World',
    'publish'
);

$posts = $wpdb->get_results( $sql );

%s表示字符串,%d表示数字。prepare()函数会将这些占位符替换为实际的值,并对这些值进行转义,从而避免SQL注入。

五、 错误处理:有备无患

$wpdb类提供了错误处理机制,可以通过$wpdb->last_error属性获取最后一次查询的错误信息。

global $wpdb;
$sql = "SELECT * FROM non_existent_table";
$wpdb->query( $sql );

if ( $wpdb->last_error ) {
    echo "查询出错:{$wpdb->last_error}";
}

六、 总结:掌握核心,玩转数据

今天咱们深入了解了$wpdb类的数据库连接与SQL查询的底层封装。 掌握了这些知识,你就可以更加灵活地操作WordPress数据库,实现各种各样的功能。

记住,$wpdb是你的专属数据库管家,善用它,让数据为你跳舞!

今天的讲座就到这里,感谢各位观众老爷的捧场!下次再见!

发表回复

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