从wpdb访问底层mysqli对象以进行自定义查询是否安全?

时间:2021-10-05 作者:Steve

我正在编写一个自定义插件(定制,用于1个客户端,不用于发布和一般消费),需要访问自定义表。

一般来说,对于WP,我遵循的原则是在可能的情况下使用更高级别的API,因此很少需要使用\\wpdb。现在我确实有了需求,我发现它是多么古老!

通过查看源代码,我似乎可以确定它使用的是mysqli(因为插件需要php 7.1,我们也控制服务器),所以我想为什么不将其用于我的自定义表逻辑?

我写了一个小包装:

<?php

namespace PluginNamespaceHere\\DB;

use mysqli;
use wpdb;

/**
 * A small wrapper class that contains, and provides direct access to, the $wpdb object,
 * as well as the underlying MYSQLI object, so we can do real prepared statements etc
 */
class DB
{

    /** @var wpdb */
    public $wpdb;

    /** @var mysqli */
    public $mysqli;

    /**
     * @param wpdb $wpdb
     */
    public function __construct(wpdb $wpdb)
    {
        $this->wpdb = $wpdb;
        //$wpdb is protected, but accessible via magic __get() wp-db.php line: 643
        $this->mysqli = $wpdb->dbh;
    }


    /**
     * Run an SQL query. If $params are provided, prepared statements are used. If $bind_types are provided, they will be
     * used in the prepared statement, if not, all params will be treated as strings
     *
     * @param string $sql The SQL string, unprefixed table names should be wrapped in curly braces eg SELECT * FROM {posts}
     * @param array $params Optional parameters for prepared statements
     * @param string $bind_types Optional bind types for prepared statements, defaults to string
     * @retun bool|mysqli_result
     */
    public function run($sql, $params=[], $bind_types=\'\')
    {
        $sql = $this->prefixTableNamesInSqlString($sql);

        if(!is_array($params) || empty($params)){
            return $this->mysqli->query($sql);
        }
        if($bind_types == \'\'){
            $bind_types = str_repeat("s", count($params));
        }
        $stmt = $this->mysqli->prepare($sql);
        $stmt->bind_param($bind_types, ...$params);
        $stmt->execute();
        return $stmt->get_result();
    }

    /**
     * Replaces curly brace table names with their actual, prefixed name
     * Eg "SELECT * from {table_name}" => "SELECT * from wp_table_name"
     * @param string $sql
     * @return string
     */
    private function prefixTableNamesInSqlString($sql)
    {
        return str_replace(["{", "}"], [$this->wpdb->prefix, ""], $sql);
    }

}
这将允许我使用真正准备好的查询,并具有良好的可读性代码,如:

<?php
$sql = "
            SELECT DISTINCT c.ID AS course_id, c.post_title AS course_title
            FROM {posts} c
            JOIN {tmsc_course_product} cp
            ON c.id = cp.course_id
            WHERE cp.product_id IN(?,?)
        ";
//Yes, i know DB::run() can return bool! Out of scope for this question
$courses = $db->run($sql, [57,4761])->fetch_all(MYSQLI_ASSOC);

我唯一担心的是,这是否会对wpdb及其家属产生连锁效应。wpdb类并不容易阅读,而且似乎包含很多状态。

2 个回复
SO网友:Jacob Peattie

这取决于您对“的定义”;“安全”;。

客户总是会成为你的客户吗?如果不是这样,那么您就是在对他们的托管环境和可用技术进行假设的基础上为他们编写代码,而这些技术可能并不总是在您的控制之下,您可能会在将来使他们的事情变得不必要的复杂化。

$wpdb 只要WordPress是最新的,无论基础技术随着时间的推移有何变化,都可能按原样工作。如果mysqli被弃用,WordPress的未来版本可能会更新$wpdb 使用任何新的和受支持的东西,但客户机将无法使用依赖于过时技术的代码。

这已经happened before 使用mysql_ 函数,当它们被弃用时。如果您使用$wpdb 那时,没有问题。但是,如果任何网站的代码直接使用了不推荐使用的函数,那么如果他们迁移到新版本的PHP,就会开始看到通知。

这种情况在短期内再次发生的可能性有多大?老实说,我不知道,只有你知道你与客户的关系,如果真的发生了,这会是一个多大的问题。但你“应该”使用$wpdb, 而不这样做确实在某些方面风险更大,即使风险很小。

坦率地说,重新发明轮子似乎没有很好地利用所有相关人员的时间。

SO网友:Mark Kaplun

是的,它是安全的,但它是一个meh。可能失败的主要事情是插件,这些插件检查正在执行的查询并报告它们(查询监视器)或缓存请求和结果。

这些工具不提供您的查询不太可能对站点产生任何可检测的影响,但这样做的正确方法是替换WPDB;“驱动程序”;使用you own,它通过添加wpdb插件来添加功能https://wpreset.com/customize-wpdb-class/ 它实现了;“正确”;准备好的语句,同时与准备好的语句的登录和缓存集成。

查询监视器仍然不起作用(它本身使用一个dropin),但其他工具应该起作用,您的DB抽象逻辑将位于一个特定的位置。

这样做明智吗?除非它是一个;必须有“;以获得性能或安全性,可能不是。