dg/dibi

此包已被弃用且不再维护。作者建议使用 dibi/dibi 包。

Dibi 是 PHP 数据库抽象库

维护者

详细信息

github.com/dg/dibi

主页

源代码

问题


README

Downloads this Month Tests Build Status Windows Latest Stable Version License

简介

PHP 的数据库访问函数没有标准化。此库隐藏了它们之间的差异,最重要的是,它为您提供了一个非常便捷的接口。

支持我

你喜欢 Dibi 吗?你在期待新功能吗?

Buy me a coffee

谢谢!

安装

通过 Composer 安装 Dibi

composer require dibi/dibi

Dibi 5.0 需要 PHP 版本 8.2,并支持 PHP 8.4。

用法

请参阅 examples 目录中的示例。Dibi 文档可在 主页 上找到。

连接到数据库

数据库连接由对象 Dibi\Connection 表示

$database = new Dibi\Connection([
	'driver'   => 'mysqli',
	'host'     => 'localhost',
	'username' => 'root',
	'password' => '***',
	'database' => 'table',
]);

$result = $database->query('SELECT * FROM users');

或者,您可以使用 dibi 静态注册,它在全局可用的存储中维护一个连接对象,并调用所有上面的函数

dibi::connect([
	'driver'   => 'mysqli',
	'host'     => 'localhost',
	'username' => 'root',
	'password' => '***',
	'database' => 'test',
	'charset'  => 'utf8',
]);

$result = dibi::query('SELECT * FROM users');

如果发生连接错误,它将抛出 Dibi\Exception

查询

我们通过方法 query() 查询数据库,它返回 Dibi\Result。行是对象 Dibi\Row

您可以在 在线游乐场 中尝试所有示例。

$result = $database->query('SELECT * FROM users');

foreach ($result as $row) {
	echo $row->id;
	echo $row->name;
}

// array of all rows
$all = $result->fetchAll();

// array of all rows, key is 'id'
$all = $result->fetchAssoc('id');

// associative pairs id => name
$pairs = $result->fetchPairs('id', 'name');

// the number of rows of the result, if known, or number of affected rows
$count = $result->getRowCount();

方法 fetchAssoc() 可以返回更复杂的关联数组。

您可以将参数轻松添加到查询中,注意问号

$result = $database->query('SELECT * FROM users WHERE name = ? AND active = ?', $name, $active);

// or
$result = $database->query('SELECT * FROM users WHERE name = ?', $name, 'AND active = ?', $active););

$ids = [10, 20, 30];
$result = $database->query('SELECT * FROM users WHERE id IN (?)', $ids);

警告:永远不要将参数连接到 SQL。这将创建一个 SQL 注入 漏洞。

$result = $database->query('SELECT * FROM users WHERE id = ' . $id); // BAD!!!

可以使用所谓的修饰符代替问号

$result = $database->query('SELECT * FROM users WHERE name = %s', $name);

如果失败,query() 抛出 Dibi\Exception 或其子类

  • ConstraintViolationException - 违反表约束
  • ForeignKeyConstraintViolationException - 无效的外键
  • NotNullConstraintViolationException - 违反 NOT NULL 条件
  • UniqueConstraintViolationException - 碰撞唯一索引

您也可以使用快捷方式

// returns associative pairs id => name, shortcut for query(...)->fetchPairs()
$pairs = $database->fetchPairs('SELECT id, name FROM users');

// returns array of all rows, shortcut for query(...)->fetchAll()
$rows = $database->fetchAll('SELECT * FROM users');

// returns row, shortcut for query(...)->fetch()
$row = $database->fetch('SELECT * FROM users WHERE id = ?', $id);

// returns field, shortcut for query(...)->fetchSingle()
$name = $database->fetchSingle('SELECT name FROM users WHERE id = ?', $id);

修饰符

除了 ? 通配符字符外,我们还可以使用修饰符

示例

$result = $database->query('SELECT * FROM users WHERE name = %s', $name);

如果 $name 为 null,则 NULL 将插入到 SQL 语句中。

如果变量是数组,修饰符将应用于其所有元素,并将它们以逗号分隔的形式插入 SQL 中

$ids = [10, '20', 30];
$result = $database->query('SELECT * FROM users WHERE id IN (%i)', $ids);
// SELECT * FROM users WHERE id IN (10, 20, 30)

当表名或列名是变量时,使用修饰符 %n。 (注意,不允许用户操作此类变量的内容)

$table = 'blog.users';
$column = 'name';
$result = $database->query('SELECT * FROM %n WHERE %n = ?', $table, $column, $value);
// SELECT * FROM `blog`.`users` WHERE `name` = 'Jim'

对于 LIKE,有三种特殊修饰符可用

搜索以字符串开头的名称

$result = $database->query('SELECT * FROM table WHERE name LIKE %like~', $query);

数组修饰符

SQL 查询中输入的参数也可以是数组。这些修饰符确定如何编译 SQL 语句

示例

$arr = [
	'a' => 'hello',
	'b'  => true,
];

$database->query('INSERT INTO table %v', $arr);
// INSERT INTO `table` (`a`, `b`) VALUES ('hello', 1)

$database->query('UPDATE `table` SET %a', $arr);
// UPDATE `table` SET `a`='hello', `b`=1

在 WHERE 子句修饰符中,可以使用 %and%or

$result = $database->query('SELECT * FROM users WHERE %and', [
	'name' => $name,
	'year' => $year,
]);
// SELECT * FROM users WHERE `name` = 'Jim' AND `year` = 1978

修饰符 %by 用于排序,键显示列,布尔值将确定是否按升序排序

$result = $database->query('SELECT id FROM author ORDER BY %by', [
	'id' => true, // ascending
	'name' => false, // descending
]);
// SELECT id FROM author ORDER BY `id`, `name` DESC

插入、更新 & 删除

我们将数据作为关联数组插入到 SQL 查询中。在这些情况下不需要修饰符和通配符 ?

$database->query('INSERT INTO users', [
	'name' => $name,
	'year' => $year,
]);
// INSERT INTO users (`name`, `year`) VALUES ('Jim', 1978)

$id = $database->getInsertId(); // returns the auto-increment of the inserted record

$id = $database->getInsertId($sequence); // or sequence value

多重插入

$database->query('INSERT INTO users', [
	'name' => 'Jim',
	'year' => 1978,
], [
	'name' => 'Jack',
	'year' => 1987,
]);
// INSERT INTO users (`name`, `year`) VALUES ('Jim', 1978), ('Jack', 1987)

删除

$database->query('DELETE FROM users WHERE id = ?', $id);

// returns the number of deleted rows
$affectedRows = $database->getAffectedRows();

更新

$database->query('UPDATE users SET', [
	'name' => $name,
	'year' => $year,
], 'WHERE id = ?', $id);
// UPDATE users SET `name` = 'Jim', `year` = 1978 WHERE id = 123

// returns the number of updated rows
$affectedRows = $database->getAffectedRows();

插入条目或如果已存在则更新

$database->query('INSERT INTO users', [
	'id' => $id,
	'name' => $name,
	'year' => $year,
], 'ON DUPLICATE KEY UPDATE %a', [ // here the modifier %a must be used
	'name' => $name,
	'year' => $year,
]);
// INSERT INTO users (`id`, `name`, `year`) VALUES (123, 'Jim', 1978)
//   ON DUPLICATE KEY UPDATE `name` = 'Jim', `year` = 1978

事务

处理事务有三个方法

$database->begin();

$database->commit();

$database->rollback();

测试

为了和Dibi玩耍一下,有一个test()方法,你可以传递像query()这样的参数,但它不是执行SQL语句,而是将它们显示在屏幕上。

可以使用$result->dump()将查询结果以表格的形式输出。

这些变量也可以使用

dibi::$sql; // the latest SQL query
dibi::$elapsedTime; // its duration in sec
dibi::$numOfQueries;
dibi::$totalTime;

复杂查询

参数也可以是一个DateTime对象。

$result = $database->query('SELECT * FROM users WHERE created < ?', new DateTime);

$database->query('INSERT INTO users', [
	'created' => new DateTime,
]);

或者SQL文。

$database->query('UPDATE table SET', [
	'date' => $database->literal('NOW()'),
]);
// UPDATE table SET `date` = NOW()

或者一个表达式,你可以使用?或修饰符。

$database->query('UPDATE `table` SET', [
	'title' => $database::expression('SHA1(?)', 'secret'),
]);
// UPDATE `table` SET `title` = SHA1('secret')

在更新时,修饰符可以直接放在键中。

$database->query('UPDATE table SET', [
	'date%SQL' => 'NOW()', // %SQL means SQL ;)
]);
// UPDATE table SET `date` = NOW()

在条件(例如,对于%and%or修饰符)中,没有必要指定键。

$result = $database->query('SELECT * FROM `table` WHERE %and', [
	'number > 10',
	'number < 100',
]);
// SELECT * FROM `table` WHERE (number > 10) AND (number < 100)

修饰符或通配符也可以用在表达式中。

$result = $database->query('SELECT * FROM `table` WHERE %and', [
	['number > ?', 10],  // or $database::expression('number > ?', 10)
	['number < ?', 100],
	['%or', [
		'left' => 1,
		'top' => 2,
	]],
]);
// SELECT * FROM `table` WHERE (number > 10) AND (number < 100) AND (`left` = 1 OR `top` = 2)

%ex修饰符将数组的所有项插入SQL。

$result = $database->query('SELECT * FROM `table` WHERE %ex', [
	$database::expression('left = ?', 1),
	'AND',
	'top IS NULL',
]);
// SELECT * FROM `table` WHERE left = 1 AND top IS NULL

SQL中的条件

条件SQL命令由三个修饰符控制%if%else%end%if必须位于表示SQL的字符串的末尾,并跟随着变量。

$user = ???

$result = $database->query('
	SELECT *
	FROM table
	%if', isset($user), 'WHERE user=%s', $user, '%end
	ORDER BY name
');

可以使用%else部分补充条件。

$result = $database->query('
	SELECT *
	FROM %if', $cond, 'one_table %else second_table
');

条件可以嵌套。

SQL中的标识符和字符串

SQL本身需要经过处理以符合数据库的约定。标识符(表和列的名称)可以输入到方括号或反引号中,字符串用单引号或双引号引用,但服务器总是发送数据库所请求的内容。例如

$database->query("UPDATE `table` SET [status]='I''m fine'");
// MySQL: UPDATE `table` SET `status`='I\'m fine'
// ODBC:  UPDATE [table] SET [status]='I''m fine'

SQL字符串中的引号内部需要重复。

结果作为关联数组

示例:以关联字段返回结果,其中键将是id字段的值。

$assoc = $result->fetchAssoc('id');

fetchAssoc()的最大功能体现在执行多个表的SQL查询,这些表具有不同类型的连接。数据库将创建一个扁平表,fetchAssoc将返回其形状。

示例:以客户和订单表(N:M关联)为例进行查询

$result = $database->query('
  SELECT customer_id, customers.name, order_id, orders.number, ...
  FROM customers
  INNER JOIN orders USING (customer_id)
  WHERE ...
');

我们希望通过客户ID和订单ID获取嵌套的关联数组

$all = $result->fetchAssoc('customer_id|order_id');

// we will iterate like this:
foreach ($all as $customerId => $orders) {
   foreach ($orders as $orderId => $order) {
	   ...
   }
}

关联描述符的语法与通过将数组赋值给PHP时的语法类似。因此,'customer_id|order_id'表示为所有行依次执行赋值序列$all[$customerId][$orderId] = $row;

有时可能会根据客户的名称而不是ID进行关联很有用。

$all = $result->fetchAssoc('name|order_id');

// the elements then proceeds like this:
$order = $all['Arnold Rimmer'][$orderId];

但如果有多位客户有相同的名称怎么办?表应该是这样的形式

$row = $all['Arnold Rimmer'][0][$orderId];
$row = $all['Arnold Rimmer'][1][$orderId];
...

因此,我们可以使用数组区分多个可能的Rimmers。关联描述符的格式与赋值类似,其中的序列数组表示[]

$all = $result->fetchAssoc('name[]order_id');

// we get all the Arnolds in the results
foreach ($all['Arnold Rimmer'] as $arnoldOrders) {
   foreach ($arnoldOrders as $orderId => $order) {
	   ...
   }
}

回到customer_id|order_id描述符的示例,我们将尝试列出每位客户的订单。

$all = $result->fetchAssoc('customer_id|order_id');

foreach ($all as $customerId => $orders) {
   echo "Customer $customerId":

   foreach ($orders as $orderId => $order) {
	   echo "ID number: $order->number";
	   // customer name is in $order->name
   }
}

同时输出客户名称也会很方便。但我们需要在$orders数组中查找它。所以,让我们调整结果形状

$all[$customerId]->name = 'John Doe';
$all[$customerId]->order_id[$orderId] = $row;
$all[$customerId]->order_id[$orderId2] = $row2;

因此,在$clientId$orderId之间,我们将插入一个中间项。这次不是我们用来区分单个Rimmers的编号索引,而是一个数据库行。解决方案非常相似,只需记住行符号表示箭头

$all = $result->fetchAssoc('customer_id->order_id');

foreach ($all as $customerId => $row) {
   echo "Customer $row->name":

   foreach ($row->order_id as $orderId => $order) {
	   echo "ID number: $order->number";
   }
}

前缀和替换

表和列名可以包含可变部分。你首先定义

// create new substitution :blog:  ==>  wp_
$database->substitute('blog', 'wp_');

然后在SQL中使用它。注意,在SQL中它们用冒号引用。

$database->query("UPDATE [:blog:items] SET [text]='Hello World'");
// UPDATE `wp_items` SET `text`='Hello World'

字段数据类型

Dibi自动检测查询列的类型,并将字段转换为原生类型。您也可以手动指定类型,您可以在Dibi\Type类中找到可能的数据类型。

$result->setType('id', Dibi\Type::Integer); // id will be integer
$row = $result->fetch();

is_int($row->id) // true

日志记录器

Dibi内置了一个日志记录器,可以跟踪所有执行的SQL语句并测量它们的持续时间。激活日志记录器

$database->connect([
	'driver'   => 'sqlite',
	'database' => 'sample.sdb',
	'profiler' => [
		'file' => 'file.log',
	],
]);

一个更通用的分析器是当连接到Nette时激活的Tracy面板。

连接到Nette

在配置文件中,我们将注册DI扩展,并为创建所需的对象以及Tracy调试器栏中的数据库面板添加dibi部分。

extensions:
	dibi: Dibi\Bridges\Nette\DibiExtension3

dibi:
	host: localhost
	username: root
	password: ***
	database: foo
	lazy: true

然后,连接对象可以从容器DI中以服务的形式获得,例如:

class Model
{
	private $database;

	public function __construct(Dibi\Connection $database)
	{
		$this->database = $database;
	}
}