【问题标题】:Database abstraction layer ontop of mysqlimysqli之上的数据库抽象层
【发布时间】:2015-05-28 09:25:36
【问题描述】:

在尝试构建健壮的数据库代码(表锁定、事务等)时,我总是对需要完成的大量代码感到恼火。

例如,两个准备好的语句中的一个事务,我想删除一个用户并在“操作”表中更新关于他的一些内容:

  1. 锁定表用户、操作
  2. 启动事务(自动提交 false)
  3. 为删除用户制作准备好的语句
  4. 检查语句是否为 != false(因为它可能在 3 处已经失败。
  5. 绑定参数
  6. 检查语句中的 errorState != "00000"(绑定参数也可能失败)
  7. 执行语句
  8. 检查语句上的 errorState != "00000"(也可能执行失败)
  9. 获取语句结果
  10. 结束声明
  11. 为更新操作创建一个新的准备好的语句
  12. 检查 if 语句 != false
  13. 绑定参数
  14. 检查语句的错误状态
  15. 执行
  16. 检查语句的错误状态
  17. 得到结果
  18. 结束声明
  19. 检查整体事务状态,如果有效提交,如果无效回滚
  20. 解锁表
  21. 将自动提交设置回 true

我就是这样做的(也许我做错了?)。如果我这样做,它的工作量很大,而且很烦人。所以我想把这些东西自动化。

我想要的是这样的:

$DB->startTransaction();
$DB->query($query);
$DB->query($query2);
$DB->query($query3);
$DB->endTransaction();

在内部,mysqli 之上的数据库抽象层将负责表锁定、准备好的语句和事务本身。我们不应该能够自动化吗?

这是我的尝试之一:

public function query($query, $table, $params = null) {
            if($params == null) {
                $this->connection->query("LOCK TABLES $table WRITE");
                $query = str_replace("!", $table, $query);
                $result = $this->connection->query($query);
                $this->connection->query("UNLOCK TABLES");
                return $result;
            }
            else {
                if (!$this->checkParams($query, $params)) {
                    return false;
                }
                $this->connection->query("LOCK TABLES $table WRITE");
                $query = str_replace("!", $table, $query);

                $stmt = $this->connection->prepare($query);
                if ($stmt != false) {
                    $typesString = "";
                    foreach ($params as $param) {
                        if (is_numeric($param)) {
                            $typesString .= "i";
                        } else if (is_double($param)) {
                            $typesString .= "d";
                        } else {
                            $typesString .= "s";
                        }
                    }

                    $finalParamArray = array($typesString);
                    $finalParamArray = array_merge($finalParamArray, $params);
                    call_user_func_array(array($stmt, "bind_param"), $this->ref($finalParamArray));
                    $this->checkStatement($stmt);
                    $stmt->execute();
                    $this->checkStatement($stmt);
                    $result = $stmt->get_result();
                    $stmt->close();
                    $this->connection->query("UNLOCK TABLES");
                    return $result;
                }
                $this->query("UNLOCK TABLES");
                return false;
            }
        }

这可以像这样调用:

$DB->query("DELETE FROM ! WHERE userID =?", "Users", array($userID));

然而,我对此没有信心。我用谷歌搜索了一下,没有找到我想要的东西。所以我现在的问题是:我想要的东西真的可能吗(应该是)?我做错了吗?

编辑: 我还有其他 2 次尝试这样做,看起来要复杂得多(300 多行代码)。如果你愿意,我也可以发布它们。但是,我仍然对它们不满意,并且不确定这是否真的正确!

【问题讨论】:

  • 为什么不使用现有的解决方案,例如 Doctrine DBAL?
  • 我用谷歌搜索了类似的东西。我很惊讶我没有找到它。谢谢,会用的。

标签: php mysqli


【解决方案1】:

你是对的,应该有一种更简单的方法来做到这一点,你说我们需要一个在 mysqli 之上的抽象层也是正确的。它不是为单独使用而设计的。

您不需要这么多步骤。特别是,您不需要检查每个方法的返回码。这应该已经消除了您的 6 个或更多步骤。您也不需要关闭语句。

绑定时无需指定类型。一直使用字符串类型。其他类型很少派上用场,几乎从不派上用场。

前段时间,我发布了一个示例,说明 mysqli 之上的抽象层可能是什么样子。

class DBClass extends mysqli {
    public function __construct(
        $host = null,
        $username = null,
        $passwd = null,
        $dbname = null,
        $port = null,
        $socket = null
    ) {
        mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
        parent::__construct($host, $username, $passwd, $dbname, $port, $socket);
        $this->set_charset('utf8mb4');
    }

    public function safeQuery(string $sql, array $params = []): ?array {
        $stmt = $this->prepare($sql);
        if ($params) {
            $stmt->bind_param(str_repeat("s", count($params)), ...$params);
        }
        $stmt->execute();
        if ($result = $stmt->get_result()) {
            return $result->fetch_all(MYSQLI_BOTH);
        }
        return null;
    }
}

这远非完美,但它显示了主要思想。您可以将准备好的语句包装在一个方法中。简单的准备/绑定/执行/get_result。而已。它可以使用和不使用参数。

在构造函数中打开连接的 3 个强制性步骤:切换错误报告、创建 mysqli 实例和设置正确的字符集。

如果你想要事务,那么你可以使用mysqli的begin_transaction()commit()。它们足够简单,不需要抽象。

我不知道你为什么觉得你需要锁表,但是这又是一个简单的 SQL 语句,不需要抽象。

$db = new DBClass('localhost', 'user', 'pass', 'test');
$db->safeQuery('LOCK TABLES users WRITE');
$db->begin_transaction();
$db->safeQuery('DELETE FROM users WHERE userID =?', [$userID]);
$db->safeQuery('DELETE FROM otherTable WHERE userID =?', [$userID2]);
$db->commit();
$db->safeQuery('UNLOCK TABLES');

【讨论】:

  • @Martin 几乎所有内容都是字符串。数字是字符串,双精度是字符串。无论如何,当执行语句时,MySQL 会将数据转换为适当的数据类型。唯一需要在 mysqli 中指定类型的时候是当您希望 MySQL 提示它是什么时,例如在ORDER BY
猜你喜欢
  • 2015-05-15
  • 1970-01-01
  • 2012-11-22
  • 2010-09-08
  • 1970-01-01
  • 1970-01-01
  • 2010-12-13
  • 1970-01-01
  • 2019-07-24
相关资源
最近更新 更多