busenov / database
PHP类库,用于简单、方便、快速且安全地与MySql数据库进行工作,使用PHP mysqli扩展和预处理查询的模拟。
Requires
- php: >=7.2
- ext-mbstring: *
- ext-mysqli: *
This package is auto-updated.
Last update: 2024-09-07 13:32:44 UTC
README
获取库
您可以作为存档下载,从本站点克隆,或通过composer(packagist.org链接)下载
composer require busenov/database
什么是 busenov/database
?
busenov/database
是一个PHP 8.0类库,用于简单、方便、快速且安全地与MySql数据库进行工作,使用PHP扩展 mysqli。
为什么需要自定义的MySql类,如果PHP已经有PDO抽象和mysqli扩展呢?
所有PHP中用于处理mysql数据库的库的主要缺点是:
- 冗长
- 开发者有两个选项来防止SQL注入
- 使用 预处理查询。
- 手动转义SQL查询主体中的参数。字符串参数通过 mysqli_real_escape_string 运行,并将期望的数字参数转换为适当的类型 -
int
和float
。
- 两种方法都有很大的缺点
- 预处理查询非常冗长。使用“现成的”PDO抽象或mysqli扩展,没有聚合所有从DBMS获取数据的方法——为了从表中获取值,您至少需要写5行代码!每个请求都是如此!
- 手动转义SQL查询主体中的参数甚至都不值得讨论。一个好的程序员是一个懒惰的程序员。一切都应该尽可能地自动化。
- 开发者有两个选项来防止SQL注入
- 无法获取用于调试的 SQL 查询
- 要了解 SQL 查询为什么在程序中不起作用,您需要调试它 - 找到逻辑错误或语法错误。要查找错误,您需要“看到”数据库对 SQL 查询本身的“咒语”,将参数代入其主体。因此,要形成高级 SQL。如果开发者使用 PDO 和预编译查询,那么它……是不可能的!在本地库中没有任何最方便的机制 未提供。只能扭曲或查看数据库日志。
解决方案:busenov/database
是一个用于操作 MySql 的类
- 消除了冗余 - 使用“本地”库执行单个请求时,您需要编写 3 行或更多代码,而现在只需一行。
- 对所有传入请求体的参数进行筛选,根据指定的占位符类型 - 防止 SQL 注入的可靠保护。
- 不会替换“本地”mysqli 适配器的功能,而只是补充它。
- 可扩展。实际上,该库只提供解析器和执行 SQL 查询的解析器,保证防止 SQL 注入。您可以从任何库类继承,并使用库机制以及
mysqli
和mysqli_result
机制来创建您需要的方法以进行操作。
busenov/database
库不是什么?
大多数数据库驱动程序的包装都是一堆无用的代码和令人厌恶的架构。它们的作者不理解它们包装的实用目的,将它们变成查询构建器(sql builder)、ActiveRecord 库和其他 ORM 解决方案。
busenov/database
库不是这些中的任何一个。这只是 MySQL DBMS 中操作常规 SQL 的方便工具 - 仅此而已!
什么是占位符?
占位符 - 特殊的 类型标记,在 SQL 查询字符串中 代替显式值(查询参数)。值本身在稍后“传递”,作为执行 SQL 查询的主要方法之后的参数。
<?php // Let's assume you installed the library via composer require './vendor/autoload.php'; use busenov\Database\Mysql; // Connecting to a DBMS and getting a "wrapper" object over mysqli - \busenov\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 \busenov\Database\Statement // \busenov\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");
现在编写查询变得容易,快速,最重要的是,busenov/database
库完全防止了任何可能的 SQL 注入。
占位符系统的介绍
以下描述了占位符类型及其用途。在了解占位符类型之前,有必要了解 busenov/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
)- 以
string
和double
类型表示的浮点数 bool
的 TRUE 转换为int(1)
,FALSE 转换为int(0)
null
转换为int(0)
- 以
- 转换为类型
double
(占位符?d
)- 以
string
和int
类型表示的整数 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 的转换规则将数值转换为字符串
null
转换为string(0) ""
- 转换为类型
null
(占位符?n
)- 任何参数。
- 对于数组、对象和资源,不允许转换。
注意! 下面对库的解释将假设已激活 MySQL::MODE_TRANSFORM
模式。
busenov/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 "\% \_"
?n
— NULL
类型占位符
忽略任何参数的值,在 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的人来说,这可能会觉得奇怪,但实现一个机制以确定在某种情况下是否需要在占位符值周围添加引号是一个非常复杂且非平凡的任务,需要编写整个解析器。
使用该库的示例
在过程中...