Dibi是PHP的数据库抽象库

维护者

详细信息

github.com/evosoftcz/dibi

主页

源代码

安装量: 3,129

依赖: 0

建议: 0

安全性: 0

星标: 0

关注者: 1

分支: 135


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.0,并支持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,则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控制。必须将%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会自动检测查询列的类型,并将字段转换为原生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;
	}
}