neat/database

Neat 数据库组件

1.5.0 2022-02-09 13:56 UTC

README

Stable Version Build Status

Neat 数据库组件提供了干净且表达性强的 API 来访问您的数据库。其核心是使用 PDO 实例的连接。

入门

要安装此软件包,只需在命令行中执行 composer

composer require neat/database

然后创建一个新的数据库连接

<?php

// Connecting is easy, just pass a PDO instance
$pdo = new PDO('mysql:host=localhost;dbname=test', 'username', 'password');
$db  = new Neat\Database\Connection($pdo);

查询

使用 queryselect 方法从数据库获取信息很简单。它们都返回一个 Result 对象,允许您通过调用同名方法来获取 row(s)value(s)

$db = new Neat\Database\Connection(new PDO('...'));

// Get all users as associative arrays in an indexed array
$users = $db->query('SELECT * FROM users')->rows();

// Get a single user row as an associative array
$user = $db->query('SELECT * FROM users WHERE id = ?', 31)->row();

// Get the id of a user
$id = $db->query('SELECT id FROM users WHERE username = ?', 'john')->value();

// Get an array of user names
$names = $db->query('SELECT username FROM users')->values();

遍历结果

在大多数情况下,当您获取多行结果时,您会想逐行遍历结果。这看起来可能很简单,但有几种方法可以实现。

$db = new Neat\Database\Connection(new PDO('...'));

// Fetching all rows before iterating over them can consume a lot of memory.
foreach ($db->query('SELECT * FROM users')->rows() as $row) {
    var_dump($row);
}

// By calling the row() method repeatedly until you hit false, you store only
// one row at a time in memory
$result = $db->query('SELECT * FROM users');
while ($row = $result->row()) {
    var_dump($row);
}

// The same can be achieved by looping over the Result directly using foreach
foreach ($db->query('SELECT * FROM users') as $row) {
    var_dump($row);
}

获取的结果

要多次使用结果(例如,先计数其行数然后返回所有行),您需要先 fetch 结果。一个活着的 query 结果不允许您这样做

$db = new Neat\Database\Connection(new PDO('...'));

// Get the fetched result first
$result = $db->fetch('SELECT * FROM users');

$count = $result->count();
$users = $result->rows();

操作

因为非选择查询从不返回结果,所以使用相同的结果 API 也没有意义。相反,您可能会喜欢处理您查询影响的行数...这正是 execute 方法提供的。

$db = new Neat\Database\Connection(new PDO('...'));

// Update a user (execute returns the number of rows affected)
$rows = $db->execute('UPDATE users SET login_at = ?', new DateTime('now'));

// Delete all inactive users
$rows = $db->execute('DELETE FROM users WHERE active = 0');

// Insert a user and get the auto_increment id value
$rows = $db->execute('INSERT INTO users (username, password) VALUES (?, ?)',
                     'john', password_hash('secret', PASSWORD_DEFAULT));

请注意,仅包含 2 个字段的插入操作已经变得相当长且难以阅读。因为这些数据操作查询通常填充了来自数组的数据,因此使用专门的 API 来完成此操作是有意义的。

进入 insertupdatedelete 方法。像 execute 方法一样,这些方法在未传递所有参数时也返回影响的行数。

$db = new Neat\Database\Connection(new PDO('...'));

// Welcome John! We'll now turn you into a database record.
if ($db->insert('users', ['username' => 'john', 'password' => '...'])) {
    $id = $db->insertedId();
}

// Update John's last login
$time = new DateTime('now');
$rows = $db->update('users', ['login_at' => $time], ['username' => 'john']);

// Delete all users that haven't logged in for a year or more
$rows = $db->delete('users', 'login_at < DATE_SUB(NOW(), INTERVAL 1 YEAR)');

请注意,插入方法不返回最后一个插入的 ID,而是您必须使用 insertedId 方法。

查询构建

为了在代码中编写查询,您可以使用查询构建器。它允许您逐步组合查询,而无需担心 SQL 语法和连接。

可以通过调用 buildselect 方法获取查询构建器。通过链式调用方法,您可以交互式地构建查询,并通过结果 API 访问结果或使用类型转换或其 get* 方法将查询转换为字符串表示。

$db = new Neat\Database\Connection(new PDO('...'));

// Look mama, without SQL!
$users = $db->select()->from('users')->where('active = 1')->query()->rows();

// Or just get the SQL... this prints "SELECT * FROM users"
echo $db->select()->from('users');

// Complex select statements are just as easy to build
$db->select('g.*, COUNT(1) as active_users')
   ->from('users', 'u')
   ->leftJoin('users_groups', 'ug', 'u.id = ug.user_id')
   ->leftJoin('groups', 'g', 'g.id = ug.group_id')
   ->where('users.active = ?', 1)
   ->groupBy('g.id')
   ->having('COUNT(u.id) > 1')
   ->orderBy('g.name')
   ->limit(25)
   ->query()
   ->rows();
   
// Mixing the order of your calls can be useful too
$query = $db->select('u.*')
            ->from('users', 'u')
            ->where('active = 1');
if (isset($searchGroup)) {
    $query->join('users_groups', 'ug', 'u.id = ug.user_id')
          ->join('groups', 'g', 'g.id = ug.group_id')
          ->where('g.name LIKE ?', "%$searchGroup%");
}

当您不传递所有参数时,insertupdatedelete 方法也返回查询构建器实例。

$db = new Neat\Database\Connection(new PDO('...'));

// A simple insert query
$db->insert('users')
   ->values(['username' => 'john', 'password' => '...'])
   ->execute();

// Or an update query
$db->update('users')
   ->set(['login_at' => new DateTime('now')])
   ->where(['username' => 'john'])
   ->execute();

转义和引用

当内置的查询构建器和占位符替换不再足够时,您很可能会最终连接自己的 SQL 查询。 quotequoteIdentifier 方法允许您在您自己的 SQL 语句中安全地嵌入文字值和标识符。

$db = new Neat\Database\Connection(new PDO('...'));

// First escape and quote the user input into an SQL safe string literal
$quoted = $db->quote('%' . $_GET['search'] . '%');
$sql = "SELECT * FROM users WHERE lastname LIKE $quoted OR firstname LIKE $quoted";

// It also understands DateTime value objects
$date = $db->quote(new DateTime('last monday'));
$sql = "SELECT * FORM users WHERE login_at > $date";

// And NULL values (be sure to use the appropriate SQL syntax, eg IS instead of =)
$null = $db->quote(null); // store 'NULL' into $null

// Identifiers can also be quoted
$table = $db->quoteIdentifier('users'); // store '`users`' (note the backticks) into $table 
$sql = "SELECT * FROM $table";

事务和锁定

如果您想在事务中运行一组数据库操作,可以使用事务方法并将操作作为闭包传递。当闭包返回时,事务将自动提交。但是,如果抛出异常,事务将自动回滚。

$db = new Neat\Database\Connection(new PDO('...'));

// When the email could not be sent, rollback the transaction
$db->transaction(function () use ($db)
{
    $db->execute('UPDATE users SET active = 0 WHERE username = ?', 'john');
    if (!mail('john@example.com', 'Confirmation', 'Account terminated')) {
        throw new \RuntimeException('E-mail failure, please rollback!');
    }
});