krugozor/database

此包最新版本(v1.0.1)没有可用的许可证信息。

PHP类库,用于简单、方便、快速、安全地与MySql数据库工作,使用PHP mysqli扩展和预处理查询的模拟。

v1.0.1 2023-09-16 15:38 UTC

This package is auto-updated.

Last update: 2024-09-17 03:37:35 UTC


README

俄语文档在此

获取库

您可以作为存档下载,从本站克隆,或通过composer下载(packagist.org链接

composer require krugozor/database

krugozor/database是什么?

krugozor/database是一个PHP >= 8.0类库,用于简单、方便、快速、安全地与MySql数据库工作,使用PHP扩展mysqli

为什么需要自己编写的MySql类,如果PHP已经有PDO抽象和mysqli扩展呢?

PHP中所有用于操作mysql数据库的库的主要缺点是:

  • 冗长
    • 开发者有两个选项来防止SQL注入
    • 这两种方法都有巨大的缺点
      • 预处理查询非常冗长。使用“现成的”PDO抽象或mysqli扩展,没有聚合所有从DBMS获取数据的方法 - 为了从表中获取值,您至少需要写5行代码!每个请求都是如此!
      • 手动转义SQL查询体中的参数甚至不讨论。优秀的程序员是懒惰的程序员。一切都应该尽可能地自动化。
  • 无法获取用于调试的 SQL 查询
    • 要了解为什么 SQL 查询在程序中不起作用,你需要对其进行调试 - 找到逻辑错误或语法错误。要找到错误,你需要“看到”数据库“诅咒”的 SQL 查询本身,并将参数代入其主体。因此,要形成高级 SQL。如果开发者使用 PDO,并且使用预编译查询,那么它... 不可能!在本地库中没有任何最便捷的机制 未提供。剩下的只能是扭曲或爬入数据库日志。

解决方案: krugozor/database 是一个用于操作 MySql 的类

  1. 消除了冗长性 - 当使用“本地”库执行一个请求时,您只需编写一行代码,而不是三行或更多。
  2. 对所有传入请求主体的参数进行筛选,根据指定的占位符类型 - 防止 SQL 注入的可靠保护。
  3. 不替代“本地”mysqli 适配器的功能,而只是补充它。
  4. 可扩展。实际上,该库仅提供解析器和执行带有 SQL 注入保证保护的 SQL 查询的功能。您可以继承任何库类,并使用库机制以及 mysqlimysqli_result 机制来创建您需要的方法以进行操作。

krugozor/database 库不是什么?

大多数数据库驱动程序的各种包装都是一堆无用的代码,具有令人厌恶的架构。它们的作者本身不理解它们包装的实用目的,将它们变成查询构建器(SQL 构建器)、ActiveRecord 库和其他 ORM 解决方案。

krugozor/database 库不是上述任何一种。这只是一个在 MySQL DBMS 中处理常规 SQL 的方便工具 - 仅此而已!

占位符是什么?

占位符 - 特殊的 类型标记,它们在 SQL 查询字符串中 代替显式值(查询参数)。而值本身是在稍后“传递”的,作为执行 SQL 查询的主方法之后的后续参数

<?php
// Let's assume you installed the library via composer
require  './vendor/autoload.php';

use Krugozor\Database\Mysql;

// Connecting to a DBMS and getting a "wrapper" object over mysqli - \Krugozor\Database\Mysql
$db = Mysql::create("localhost", "root", "password")
      // Error output language - English
      ->setErrorMessagesLang('en')
      // Database selection
      ->setDatabaseName("test")
      // Encoding selection
      ->setCharset("utf8")
      // Enable storage of all SQL queries for reporting/debugging/statistics
      ->setStoreQueries(true);

// Getting a result object \Krugozor\Database\Statement
// \Krugozor\Database\Statement - "wrapper" over an object mysqli_result
$result = $db->query("SELECT * FROM `users` WHERE `name` = '?s' AND `age` = ?i", "d'Artagnan", 41);

// We receive data (in the form of an associative array, for example)
$data = $result->fetchAssoc();

// SQL query not working as expected?
// Not a problem - print it and see the generated SQL query,
// which will already be with the parameters substituted into its body:
echo $db->getQueryString(); // SELECT * FROM `users` WHERE `name` = 'd\'Artagnan' AND `age` = 41

通过占位符系统传递的 SQL 查询参数将由根据占位符类型的特殊转义机制进行处理。也就是说,你不再需要将变量包装在转义函数类型 mysqli_real_escape_string() 中,或者像以前那样将它们转换为数字类型

<?php
// Previously, before each request to the DBMS, we did
// something like this (and many people still don't do it):
$id = (int) $_POST['id'];
$value = mysqli_real_escape_string($mysql, $_POST['value']);
$result = mysqli_query($mysql, "SELECT * FROM `t` WHERE `f1` = '$value' AND `f2` = $id");

现在编写查询变得容易了,快速,最重要的是,krugozor/database 库完全防止了任何可能的 SQL 注入。

占位符系统简介

下文描述了占位符类型及其用途。在了解占位符类型之前,有必要了解 krugozor/database 库的工作机制。示例

 $db->query("SELECT ?i", 123); 

模板转换后的 SQL 查询

SELECT 123

在执行此命令时 库检查参数 123 是否为整数值。占位符 ?i 是字符 ?(问号)和单词 integer 的第一个字母。如果参数确实是整型数据类型,则 SQL 查询模板中的占位符 ?i 被替换为值 123,然后 SQL 被传递以执行。

由于 PHP 是一种弱类型语言,上述表达式等同于以下

 $db->query("SELECT ?i", '123'); 

模板转换后的 SQL 查询

SELECT 123

也就是说,数字(整数和浮点数)在类型和形式上都被表示为 string,从库的角度来看是等效的。

库模式和强制类型转换

库有两种工作模式

  • Mysql::MODE_STRICT - 占位符类型和参数类型严格匹配模式。在 Mysql::MODE_STRICT 模式下,参数必须匹配占位符类型。例如,尝试将值 55.5'55.5' 作为整数占位符 ?i 的参数传递将导致抛出异常
// set strict mode
$db->setTypeMode(Mysql::MODE_STRICT);
// this expression will not be executed, an exception will be thrown:
// Trying to set placeholder type "int" to value type "double" in query template "SELECT ?i"
$db->query('SELECT ?i', 55.5);
  • Mysql::MODE_TRANSFORM — 当占位符类型和参数类型不匹配时,将参数类型转换为占位符类型的模式。 默认情况下设置 Mysql::MODE_TRANSFORM 模式,这是一个“宽容”的模式——如果占位符类型和参数类型不匹配,它不会抛出异常,而是 尝试使用 PHP 语言本身将参数转换为所需的占位符类型。顺便说一句,作为库的作者,我始终使用这种特定模式,我在实际工作中从未使用过严格模式(Mysql::MODE_STRICT),但你可能需要它。

Mysql::MODE_TRANSFORM 中允许以下转换

  • 转换为类型 int(占位符 ?i
    • stringdouble 类型表示的浮点数
    • bool 的 TRUE 转换为 int(1),FALSE 转换为 int(0)
    • null 转换为 int(0)
  • 转换为类型 double(占位符 ?d
    • stringint 类型表示的整数
    • bool 的 TRUE 变为 float(1),FALSE 变为 float(0)
    • null 转换为 float(0)
  • 转换为类型 string(占位符 ?s
    • bool 的 TRUE 转换为 string(1) "1",FALSE 转换为 string(1) "0"。这种行为与 PHP 中将 bool 转换为 int 的行为不同,因为在实践中,布尔类型通常在 MySQL 中表示为数字。
    • 根据 PHP 的转换规则将 numeric 值转换为字符串
    • null 转换为 string(0) ""
  • 转换为类型 null(占位符 ?n
    • 任何参数。
  • 对于数组、对象和资源,不允许转换。

注意! 下面对库的解释将假设已激活 Mysql::MODE_TRANSFORM 模式。

krugozor/database 库提供了哪些类型的占位符?

?i — 整数占位符

$db->query('SELECT * FROM `users` WHERE `id` = ?i', $_POST['user_id']); 

注意! 如果你操作的是超出 PHP_INT_MAX 范围的数字,那么

  • 在程序中将它们唯一地作为字符串操作。
  • 不要使用此占位符,使用字符串占位符 ?s(见下文)。关键是超出限制 PHP_INT_MAX 的数字,PHP 将其解释为浮点数。库解析器将尝试将参数转换为类型 int,因此,“结果将是未定义的,因为浮点数没有足够的精度来返回正确的结果。在这种情况下,甚至不会显示警告或注释!”—— php.net

?d — 浮点数占位符

$db->query('SELECT * FROM `prices` WHERE `cost` = ?d', 12.56); 

注意! 如果你使用库来处理 double 数据类型,请设置适当的区域设置,以确保在 PHP 级别和 DBMS 级别整数部分和小数部分的分隔符相同。

?s — 字符串类型占位符

使用 mysqli::real_escape_string() 方法对参数值进行转义

 $db->query('SELECT "?s"', "You are all fools, and I am d'Artagnan!");

模板转换后的 SQL 查询

SELECT "You are all fools, and I am d\'Artagnan!"

?S — 用于 SQL LIKE 操作符替换的字符串类型占位符

使用 mysqli::real_escape_string() 方法进行转义 + 转义 LIKE 操作符中使用的特殊字符(%_

 $db->query('SELECT "?S"', '% _'); 

模板转换后的 SQL 查询

SELECT "\% \_"

?nNULL 类型占位符

忽略任何参数的值,在 SQL 查询中将占位符替换为字符串 NULL

 $db->query('SELECT ?n', 123); 

模板转换后的 SQL 查询

SELECT NULL

?A* — 从关联数组生成的关联集占位符,生成形式为 key = value 的序列对

其中字符*是占位符之一

  • i(整数占位符)
  • d(浮点数占位符)
  • s(字符串类型占位符)

转换和转义的规则与上面描述的单个标量类型相同。示例

$db->query('INSERT INTO `test` SET ?Ai', ['first' => '123', 'second' => 1.99]);

模板转换后的 SQL 查询

INSERT INTO `test` SET `first` = "123", `second` = "1"

?a* - 从简单(或关联)数组设置占位符,生成一系列值

其中*是以下类型之一

  • i(整数占位符)
  • d(浮点数占位符)
  • s(字符串类型占位符)

转换和转义的规则与上面描述的单个标量类型相同。示例

 $db->query('SELECT * FROM `test` WHERE `id` IN (?ai)', [123, 1.99]);

模板转换后的 SQL 查询

 SELECT * FROM `test` WHERE `id` IN ("123", "1")

?A[?n, ?s, ?i, ...] — 带有显式类型和参数数量指示的关联数组占位符,生成一系列 key = value

示例

$db->query('INSERT INTO `users` SET ?A[?i, "?s"]', ['age' => 41, 'name' => "d'Artagnan"]);

模板转换后的 SQL 查询

 INSERT INTO `users` SET `age` = 41,`name` = "d\'Artagnan"

?a[?n, ?s, ?i, ...] — 带有显式类型和参数数量指示的数组占位符,生成一系列值

示例

$db->query('SELECT * FROM `users` WHERE `name` IN (?a["?s", "?s"])', ['Daniel O"Neill', "d'Artagnan"]);

模板转换后的 SQL 查询

 SELECT * FROM `users` WHERE `name` IN ("Daniel O\"Neill", "d\'Artagnan")

?f — 表或字段名称占位符

此占位符用于在查询中作为参数传递表或字段名称的情况。字段和表名称用撇号括起来

 $db->query('SELECT ?f FROM ?f', 'name', 'database.table_name');

模板转换后的 SQL 查询

 SELECT `name` FROM `database`.`table_name`

分隔引号

该库要求程序员遵循SQL语法。 这意味着以下查询将无法工作

$db->query('SELECT CONCAT("Hello, ", ?s, "!")', 'world');

— 占位符 ?s 必须用单引号或双引号括起来

$db->query('SELECT concat("Hello, ", "?s", "!")', 'world');

模板转换后的 SQL 查询

SELECT concat("Hello, ", "world", "!")

对于习惯使用PDO工作的人来说,这可能会显得奇怪,但实现一个机制来决定在某种情况下是否需要将占位符值用引号括起来是一个非常复杂的任务,需要编写一个完整的解析器。

该库的使用示例

在过程中...