rawsrc / pdo-plus-plus
Requires
- php: >=8.0
- ext-pdo: *
README
2022-11-04
PHP 8.0+
v.5.0.1
一个类中的PHP完整对象PDO包装器
PDOPlusPlus
(别名PPP
)是一个PHP的单类PDO包装器,具有革命性的流体SQL语法。您不再需要以传统方式使用PDO,可以完全省略prepare()
、bindValue()
、bindParam()
的概念。这些机制的使用现在被PDOPlusPlus
隐藏。您只需直接编写干净的SQL查询,并直接注入您的值。
引擎将自动转义值,并让您只关注SQL语法。
PDOPlusPlus
完全符合
- INSERT
- UPDATE
- DELETE
- SELECT
- 存储过程
- 事务(即使是嵌套的)
- 本地SQL BIGINT(或INT8)有符号/无符号支持
对于存储过程,您可以使用任何IN
、OUT
或INOUT
参数。
PDOPlusPlus
还完全兼容一次性返回多个数据集的情况。
注意:PDOPlusPlus
不会验证任何值
PDO的瑞士军刀。
安装
composer require rawsrc/pdoplusplus
概念
PDOPlusPlus
的力量直接关联到使用PHP魔法函数__invoke()
作为函数调用的实例方式
您只需选择正确的注入器,它会以安全的方式处理要注入SQL中的值。
为了覆盖所有用例,有6种不同的注入器
getInjectorIn()
:注入的值直接转义(普通SQL)。这是默认注入器getInjectorInByVal()
:注入的值使用PDOStatement->bindValue()
机制转义getInjectorInByRef()
:注入的值使用PDOStatement->bindParam()
机制转义getInjectorInAsRef()
:值通过引用传递并直接转义(普通SQL)getInjectorOut()
:用于只有OUT参数的存储过程getInjectorInOut()
:用于INOUT参数的存储过程,IN参数直接转义(普通SQL)
请注意,默认情况下,PDOPlusPlus
将以普通SQL转义您的值。如果您想要其他行为,例如使用PDOStatement
或调用存储过程,则必须使用特定的注入器。
从版本4.0的变更日志
这个5.0.x版本是一个重大更新,可能与基于4.x版本的代码略有兼容性问题
新功能
- 完全支持
BIGINT/INT8
数据类型(SIGNED/UNSIGNED
) - 新注入器:
getInjectorInAsRef()
:值通过引用传递并直接转义(普通SQL) - 删除一些
float
数据类型的别名:double
、num
、numeric
,只有float
仍然可用
已删除
- 在创建注入器时定义最终数据类型
AbstractInjector
类,因为它的代码非常简单,可以直接在每个注入器中实现。因此,现在PDOPlusPlus
是一个真正的独立类,没有其他依赖项
测试代码现已可用。所有测试都是针对我的另一个项目编写的:Exacodis,一个用于PHP的极简测试引擎
自动重置功能
以前,您必须为要执行的每个语句创建一个新的 PDOPlusPlus
实例。使用自动重置功能(默认启用),您可以重复使用同一个 PDOPlusPlus
实例,次数不限。
以下两种情况下会自动禁用自动重置
- 如果语句失败
- 如果存在任何引用变量
在这些情况下,实例将保留已定义的数据和参数。
您必须使用以下方式强制重置实例:$ppp->reset();
除了事务中的保存点外,所有内容都被清理了,这些保存点通过 $ppp->releaseAll();
重置
您可以使用以下方式激活/禁用此功能
$ppp->setAutoResetOn()
$ppp->setAutoResetOff()
关于注入器
允许的不同数据类型有:int str float bool binary bigint
每个注入器都可以使用自己的参数调用。
getInjectorIn(mixed $value, string $type = 'str')
getInjectorInAsRef(mixed &$value, string $type = 'str')
getInjectorInByVal(mixed $value, string $type = 'str')
getInjectorInByRef(mixed &$value, string $type = 'str')
getInjectorOut(string $out_tag)
getInjectorInOut(mixed $value, string $inout_tag, string $type = 'str')
请注意,binary 和 bigint 数据类型与其他类型类似。只是在引擎内部,处理过程不同。
请参考以下如何在 SQL 上下文中使用它们的示例。
数据库连接
正如所写,PDOPlusPlus
是一个 PDO 包装器,因此它当然需要使用 PDO 连接到您的数据库。您可以声明必要的连接配置文件。每个连接都有一个唯一的 ID。
// first profile: power user PDOPlusPlus::addCnxParams( cnx_id: 'user_root', params: [ 'scheme' => 'mysql', 'host' => 'localhost', 'database' => '', 'user' => 'root', 'pwd' => '**********', 'port' => '3306', 'timeout' => '5', 'pdo_params' => [], 'dsn_params' => [] ], is_default: true ); // second profile: basic user PDOPlusPlus::addCnxParams( cnx_id: 'user_test', params: [ 'scheme' => 'mysql', 'host' => 'localhost', 'database' => 'db_pdo_plus_plus', 'user' => 'user_test', 'pwd' => '**********', 'port' => '3306', 'timeout' => '5', 'pdo_params' => [], 'dsn_params' => [] ], is_default: false );
您可以在初始化新实例时定义用于在服务器上执行 SQL 的连接,例如:$ppp = new PDOPlusPlus('user_root');
或 $ppp = new PDOPlusPlus('user_test');
。
如果省略了 ID,则默认将使用该连接。一旦定义了默认连接的 ID,也可以更改它,请参阅:$ppp->setDefaultConnection();
让我们玩一会儿
为了课程,我将使用一个非常简单的包含一个表的数据库。
DROP DATABASE IF EXISTS db_pdo_plus_plus; CREATE DATABASE db_pdo_plus_plus; USE db_pdo_plus_plus; CREATE TABLE t_video ( video_id int auto_increment primary key, video_title varchar(255) not null, video_support varchar(30) not null comment 'DVD DIVX BLU-RAY', video_multilingual tinyint(1) default 0 not null, video_chapter int null, video_year int not null, video_summary text null, video_stock int default 0 not null, video_img mediumblob null, video_bigint_unsigned bigint unsigned null, video_bigint bigint null, constraint t_video_video_titre_index unique (video_title) );
示例数据集
$data = [[ 'title' => "The Lord of the Rings - The Fellowship of the Ring", 'support' => 'BLU-RAY', 'multilingual' => true, 'chapter' => 1, 'year' => 2001, 'summary' => null, 'stock' => 10, 'bigint_unsigned' => '18446744073709551600', 'bigint_signed' => -9223372036854775000, ], [ 'title' => "The Lord of the Rings - The two towers", 'support' => 'BLU-RAY', 'multilingual' => true, 'chapter' => 2, 'year' => 2002, 'summary' => null, 'stock' => 0, 'bigint_unsigned' => '18446744073709551600', 'bigint_signed' => -9223372036854775000, ], [ 'title' => "The Lord of the Rings - The return of the King", 'support' => 'DVD', 'multilingual' => true, 'chapter' => 3, 'year' => 2003, 'summary' => null, 'stock' => 1, 'bigint_unsigned' => '18446744073709551600', 'bigint_signed' => -9223372036854775000, ]];
添加记录
让我们使用 PDOPlusPlus
将第一部电影添加到数据库中。
我将使用 SQL 直接模式,省略 PDOStatement
步骤。
include 'PDOPlusPlus.php'; $ppp = new PDOPlusPlus(); // here the default connection wil be used and the auto-reset is enabled $film = $data[0]; $sql = <<<sql INSERT INTO t_video ( video_title, video_support, video_multilingual, video_chapter, video_year, video_summary, video_stock, video_bigint_unsigned, video_bigint_signed ) VALUES ( {$ppp($film['title'])}, {$ppp($film['support'])}, {$ppp($film['multilingual'], 'bool')}, {$ppp($film['chapter'], 'int')}, {$ppp($film['year'], 'int')}, {$ppp($film['summary'])}, {$ppp($film['stock'], 'int')}, {$ppp($film['bigint_unsigned'], 'bigint')}, {$ppp($film['bigint_signed'], 'bigint')} ) sql; $new_id = $ppp->insert($sql); // $new_id = '1'
让我们使用 PDOPlusPlus
将第二部电影添加到数据库中。
这次,我将使用基于值的 PDOStatement
(->bindValue()
)。
$in = $ppp->getInjectorInByVal(); $film = $data[1]; $sql = <<<sql INSERT INTO t_video ( video_title, video_support, video_multilingual, video_chapter, video_year, video_summary, video_stock, video_bigint_unsigned, video_bigint_signed ) VALUES ( {$in($film['title'])}, {$in($film['support'])}, {$in($film['multilingual'], 'bool')}, {$in($film['chapter'], 'int')}, {$in($film['year'], 'int')}, {$in($film['summary'])}, {$in($film['stock'], 'int')}, {$in($film['bigint_unsigned'], 'bigint')}, {$in($film['bigint_signed'], 'bigint')} ) sql; $new_id = $ppp->insert($sql); // $new_id = '2'
让我们截断表,然后一次性添加整部电影列表。
这次,我将使用基于引用的 PDOStatement
(->bindParam()
),因为有很多迭代要做。我将使用 ->injectorInByRef();
返回的注入器。
$ppp->execute('TRUNCATE TABLE t_video'); $in = $ppp->getInjectorInByRef(); $sql = <<<sql INSERT INTO t_video ( video_title, video_support, video_multilingual, video_chapter, video_year, video_summary, video_stock, video_bigint_unsigned, video_bigint_signed ) VALUES ( {$in($title)}, {$in($support)}, {$in($multilingual, 'bool')}, {$in($chapter, 'int')}, {$in($year, 'int')}, {$in($summary)}, {$in($stock, 'int')}, {$in($bigint_unsigned, 'bigint')}, {$in($bigint_signed, 'bigint')} ) sql; foreach ($data as $film) { extract($film); // destructuring the array into components used to populate the references declared just above $ppp->insert($sql); }
请注意,前面的语句有 "by ref" 变量,且在这种情况下禁用了自动重置。
更新记录
因此,为了能够重复使用同一个 PDOPlusPlus
实例,我们首先必须清理它。
// we clean the instance $ppp->reset(); $id = 1; $support = 'DVD'; $sql = "UPDATE t_video SET video_support = {$ppp($support)} WHERE video_id = {$ppp($id, 'int')}"; $nb = $ppp->update($sql); // nb of affected rows
删除记录
$id = 1; $sql = "DELETE FROM t_video WHERE video_id = {$ppp($id, 'int')}"; $nb = $ppp->delete($sql); // nb of affected rows
选择一条记录
$id = 1; $sql = "SELECT * FROM t_video WHERE video_id = {$ppp($id, 'int')}"; $data = $ppp->select($sql);
$sql = "SELECT * FROM t_video WHERE video_support LIKE {$ppp('%RAY%')}"; $data = $ppp->select($sql);
如果您需要从查询中提取数据的一种更强大方式,有一个特定的方法 selectStmt()
,它允许您访问由引擎生成的 PDOStatement
。
$sql = "SELECT * FROM t_video WHERE video_support LIKE {$ppp('%RAY%')}"; $stmt = $ppp->selectStmt($sql); $data = $stmt->fetchAll(PDO::FETCH_OBJ);
还可以拥有可滚动的游标(在这里您也可以访问由引擎创建的 PDOStatement
)
$sql = "SELECT * FROM t_video WHERE video_support LIKE {$ppp('%RAY%')}"; $stmt = $ppp->selectStmtAsScrollableCursor($sql); while ($row = $stmt->fetch(PDO::FETCH_NUM, PDO::FETCH_ORI_NEXT)) { // ... // }
绑定列
从 v.4.0.0 版本开始,可以定义绑定列,就像使用 PDOStatement->bindColumn(...)
一样。当您特别处理二进制数据时,这很有用。
此功能仅适用于 $ppp->selectStmt()
和 $ppp->selectStmtAsScrollableCursor()
。
// First, you have to prepare the bound variables. $columns = [ 'video_title' => [&$video_title, 'str'], // watch carefully the & before the var 'video_img' => [&$video_img, 'binary'], // watch carefully the & before the var ]; // you have to declare into the instance the bound columns $ppp->setBoundColumns($columns); // then call the selectStmt() $ppp->selectStmt("SELECT video_title, video_img FROM t_video WHERE video_id = {$ppp(1, 'int')}"); // then read the result while ($row = $stmt->fetch(PDO::FETCH_BOUND)) { // here $video_title and $video_img are available and well defined }
BIGINT 或 INT8 列
从 v.5.0.0 版本开始,引擎完全符合 SQL 的 BIGINT
或 INT8
(有符号或无符号)数据类型。在内部,即使您必须在 PHP 世界中将它们作为字符串处理,引擎也会始终发送真正的 bigint 到 sql 引擎。这也适用于
使用 PDO 绑定机制注入器。引擎为这些特定用例实现了一个解决方案,因此对开发者来说是透明的,开发者只需要声明任何注入值的类型为 bigint
。
由于整数核心限制(PHP_INT_MIN
和 PHP_INT_MAX
),您无法定义一个变量如 $int = 18446744073709551600;
,PHP 核心会自动将该值转换为浮点数 $int = 1.844674407371E+19
。在 PDOPlusPlus
之前,除非您将它们视为字符串,否则很难在 PHP 上下文中轻松使用,而在 SQL 世界中却可以。
请记住,当您从数据库中选择一个无符号 bigint 列时,如果值严格大于 PHP_INT_MAX
,则将检索一个字符串,否则将检索一个真正的整数。通常,对于有符号 bigint 列,SQL 限制与 PHP 核心限制相匹配,因为通常两者都在运行 x64 架构。
存储过程
由于有可能一次性提取多个数据集或/和传递多个参数 IN
、OUT
或 INOUT
,您通常需要使用以下所示的特殊值注入器。
一个数据集
让我们创建一个仅返回简单数据集的 SP
$ppp = new PPP(); $exec = $ppp->execute(<<<'sql' CREATE OR REPLACE DEFINER = root@localhost PROCEDURE db_pdo_plus_plus.sp_list_films() BEGIN SELECT * FROM t_video; END; sql );
现在,调用它
$rows = $ppp->call('CALL sp_list_films()', true); // the true tells PPP that SP is a query // $rows is a multidimensional array: // $rows[0] => for the first dataset which is an array of all films
一次性返回两个数据集
让我们创建一个一次返回双数据集的 SP
// TWO ROWSET $exec = $ppp->execute(<<<'sql' CREATE OR REPLACE DEFINER = root@localhost PROCEDURE db_pdo_plus_plus.sp_list_films_group_by_support() BEGIN SELECT * FROM t_video WHERE video_support = 'BLU-RAY'; SELECT * FROM t_video WHERE video_support = 'DVD'; END; sql );
现在,调用它
$rows = $ppp->call('CALL sp_list_films_group_by_support()', true); // the true tells PPP that SP is a query // $rows is a multidimensional array: // $rows[0] => for the first dataset which is an array of films (BLU-RAY) // $rows[1] => for the second dataset which is an array of films (DVD)
一个 IN 参数
让我们创建一个有一个 IN 参数的 SP
// WITH ONE IN PARAM $exec = $ppp->execute(<<<'sql' CREATE OR REPLACE DEFINER = root@localhost PROCEDURE db_pdo_plus_plus.sp_list_films_one_in_param( p_support VARCHAR(30) ) BEGIN SELECT * FROM t_video WHERE video_support = p_support; END; sql ); // AND CALL IT // FIRST METHOD : plain sql $rows = $ppp->call("CALL sp_list_films_one_in_param({$ppp('DVD')})", true); // $rows is a multidimensional array: // $rows[0] => for the first dataset which is an array of films (DVD) // EXACTLY THE SAME USING ->bindValue() $in = $ppp->getInjectorInByVal(); $rows = $ppp->call("CALL sp_list_films_one_in_param({$in('DVD')})", true); // AND IF YOU WANT TO USE A REFERENCE INSTEAD $in = $ppp->getInjectorInByRef(); $sup = 'DVD'; $rows = $ppp->call("CALL sp_list_films_one_in_param({$in($sup)})", true); $ppp->reset(); // do not forget to reset the instance to be able to reuse it
直接在 SQL 中链变量,根据您需要传递给存储过程的 IN 参数数量。
一个 OUT 参数
让我们创建一个有一个 OUT
参数的 SP
// WITH ONE OUT PARAM $exec = $ppp->execute(<<<'sql' CREATE OR REPLACE DEFINER = root@localhost PROCEDURE db_pdo_plus_plus.sp_nb_films_one_out_param( OUT p_nb INT ) BEGIN SELECT COUNT(video_id) INTO p_nb FROM t_video; END; sql );
并使用针对 OUT
参数的特定注入器调用它
$out = $ppp->getInjectorOut(); $exec = $ppp->call("CALL sp_nb_films_one_out_param({$out('@nb')})", false); $nb = $exec['out']['@nb'];
请注意,所有 OUT
值都始终存储在结果数组中,键为 out
一个数据集和两个 OUT 参数
也可以混合数据集和 OUT
参数
// WITH ROWSET AND TWO OUT PARAM $exec = $ppp->execute(<<<'sql' CREATE OR REPLACE DEFINER = root@localhost PROCEDURE db_pdo_plus_plus.sp_nb_films_rowset_two_out_param( OUT p_nb_blu_ray INT, OUT p_nb_dvd INT ) BEGIN SELECT * FROM t_video ORDER BY video_year DESC; SELECT COUNT(video_id) INTO p_nb_blu_ray FROM t_video WHERE video_support = 'BLU-RAY'; SELECT COUNT(video_id) INTO p_nb_dvd FROM t_video WHERE video_support = 'DVD'; END; sql ); $out = $ppp->getInjectorOut(); $exec = $ppp->call("CALL sp_nb_films_rowset_two_out_param({$out('@nb_blu_ray')}, {$out('@nb_dvd')})", true); $rows = $exec[0]; // $exec[0] => for the first dataset which is an array of all films ordered by year DESC $nb_br = $exec['out']['@nb_blu_ray']; // note the key 'out' $nb_dv = $exec['out']['@nb_dvd'];
带有两个输出参数的单输入输出参数
最后,让我们创建一个SP,它使用INOUT
和OUT
参数的混合
// WITH ONE INOUT PARAM AND TWO OUT PARAM $exec = $ppp->execute(<<<'sql' CREATE OR REPLACE DEFINER = root@localhost PROCEDURE db_pdo_plus_plus.sp_nb_films_one_inout_two_out_param( INOUT p_qty INT, OUT p_nb_blu_ray INT, OUT p_nb_dvd INT ) BEGIN DECLARE v_nb INT; SELECT SUM(video_stock) INTO v_nb FROM t_video; SET p_qty = v_nb - p_qty; SELECT COUNT(video_id) INTO p_nb_blu_ray FROM t_video WHERE video_support = 'BLU-RAY'; SELECT COUNT(video_id) INTO p_nb_dvd FROM t_video WHERE video_support = 'DVD'; END; sql );
并使用特定的注入器来调用它:一个用于INOUT
参数,另一个用于OUT
参数。
请小心处理INOUT
注入器的语法。
$io = $ppp->getInjectorInOut(); // io => input/output $out = $ppp->getInjectorOut(); $exec = $ppp->call("CALL sp_nb_films_one_inout_two_out_param({$io('25', '@stock', 'int')}, {$out('@nb_blu_ray')}, {$out('@nb_dvd')})", false); $stock = $exec['out']['@stock']; $nb_br = $exec['out']['@nb_blu_ray']; $nb_dv = $exec['out']['@nb_dvd'];
事务
PDO++完全兼容RDBS事务机制。
您有几种方法可以帮助您管理SQL代码流
setTransaction()
用于定义即将执行的事务的执行上下文startTransaction()
commit()
rollback()
将回滚到最后一个保存点rollbackTo()
将回滚到指定的保存点rollbackAll()
将回滚到开始处savePoint()
用于创建一个新的保存点(SQL代码流中的一个标记)release()
用于删除保存点releaseAll()
用于删除所有保存点
如果您熟悉SQL事务理论,这些函数的命名很好,易于理解。
请注意,当您开始一个事务时,引擎将禁用数据库AUTOCOMMIT
参数,这样,所有的SQL语句都将一次性在$ppp->commit();
上保存。
错误
为了避免大量的try { } catch { }
块,我引入了一种机制来简化这部分代码。
由于PDOPlusPlus
在语句失败时可能会抛出Exception
,您应该在代码的任何地方都拦截这个可能的问题并使用一个try { } catch { }
块。这很重,不是吗?
现在,您可以定义一个闭包,该闭包将包含异常的处理。最初,您只需定义一次唯一的闭包,该闭包将接收并处理由PDOPlusPlus
抛出的Exception
// Exception wrapper for PDO PDOPlusPlus::setExceptionWrapper(function(Exception $e, PDOPlusPlus $ppp, string $sql, string $func_name, ...$args) { // here you code whatever you want // ... // then you must return a result return 'DB Error, unable to execute the query'; });
然后您可以使用以下方式激活/停用此功能:
$ppp->setThrowOn();
$ppp->setThrowOff();
在出现问题时,如果抛出功能被禁用,PDOPlusPlus
将像往常一样拦截Exception
并将其传递到您的闭包。在这种情况下,该方法将返回null
。
假设这段代码产生了错误
try { $ppp = new PDOPlusPlus(); $sql = "INSERT INTO t_table (field_a, field_b) VALUES ({$ppp('value_a')}, {$ppp('value_b')})"; $id = $ppp->insert($sql); } catch (Exception $e) { // bla bla }
使用异常包装器机制,您可以简单地做
$ppp = new PDOPlusPlus(); $ppp->setThrowOff(); $sql = "INSERT INTO t_table (field_a, field_b) VALUES ({$ppp('value_a')}, {$ppp('value_b')})"; $id = $ppp->insert($sql); if ($id === null) { $error = $ppp->getErrorFromWrapper(); // $error = 'DB Error, unable to execute the query' }
结论
希望这能帮助您以更舒适的方式生成更好的SQL代码,并在PHP代码中原生使用PDO。
好的,伙计们,这就是全部内容。享受吧!
原始源代码