dibi/dibi

Dibi是PHP的数据库抽象库

维护者

详细信息

github.com/dg/dibi

主页

源码

问题

安装: 3,058,039

依赖者: 132

建议者: 11

安全: 0

星标: 488

关注者: 44

分支: 135

开放问题: 32


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 - 违反非空条件
  • 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,则在SQL语句中插入NULL。

如果变量是数组,修饰符将应用于其所有元素,并用逗号分隔插入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控制。在字符串表示的SQL末尾必须使用%if,后跟变量

$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会自动检测查询列的类型,并将字段转换为原生PHP类型。我们也可以手动指定类型。可以在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',
	],
]);

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

连接到 Nette

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

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;
	}
}