Статьи

Еще один уровень абстракции базы данных с PHP и DBAL

Я не большой поклонник ОРМ. Я чувствую себя очень комфортно, работая с необработанными SQL, и из-за этого я обычно использую DBAL (или PDO в старых проектах). У меня есть одна небольшая библиотека для ежедневных операций с базами данных, и сегодня я написал эту библиотеку

Прежде всего, представьте одно соединение DBAL. В этом примере я использую базу данных sqlite in-memomy, но мы можем использовать любую базу данных, поддерживаемую DBAL (она же «почти все»):

use Doctrine\DBAL\DriverManager;
 
$conn = DriverManager::getConnection([
    'driver' => 'pdo_sqlite',
    'memory' => true
]);

Мы также можем создать одно соединение DBAL из соединения PDO. Полезно использовать DBAL в унаследованных приложениях вместо создания нового соединения (помните, что DBAL работает поверх PDO)

use Doctrine\DBAL\DriverManager;
 
$conn = DriverManager::getConnection(['pdo' => new PDO('sqlite::memory:')]);

Теперь мы настроили базу данных для примера

$conn->exec("CREATE TABLE users (
            userid VARCHAR PRIMARY KEY  NOT NULL ,
            password VARCHAR NOT NULL ,
            name VARCHAR,
            surname VARCHAR
            );");
$conn->exec("INSERT INTO users VALUES('user','pass','Name','Surname');");
$conn->exec("INSERT INTO users VALUES('user2','pass2','Name2','Surname2');");

Наша таблица «пользователи» имеет две записи. Теперь мы можем начать использовать нашу библиотеку.

Сначала мы создаем новый экземпляр нашей библиотеки:

use G\Db;
 
$db = new Db($conn);

Теперь простой запрос из строки:

$data = $db->select("select * from users");

Иногда мне лень и я не хочу писать всю строку SQL, и я хочу выполнить select * from table:

use G\Sql;
$data = $db->select(SQL::createFromTable("users"));

Возможно, нам нужно отфильтровать наш оператор Select с предложением WHERE:

$data = $db->select(SQL::createFromTable("users", ['userid' => 'user2']));

А теперь кое-что очень интересное (по крайней мере, для меня). Я хочу перебрать набор записей и, возможно, изменить его. Конечно, я могу использовать «foreach» над $ data и делать все, что мне нужно, но я предпочитаю использовать следующий синтаксис:

$data = $db->select(SQL::createFromTable("users"), function (&$row) {
    $row['name'] = strtoupper($row['name']);
});

Для меня это более читабельно. Я перебираю набор записей и меняю имя строки на верхний регистр. Здесь вы можете увидеть, что делает моя функция «выбор»:

/**
* @param Sql|string $sql
* @param \Closure $callback
* @return array
*/
public function select($sql, \Closure $callback = null)
{
    if ($sql instanceof Sql) {
        $sqlString = $sql->getString();
        $parameters = $sql->getParameters();
        $types = $sql->getTypes();
    } else {
        $sqlString = $sql;
        $parameters = [];
        $types = [];
    }
 
    $statement = $this->conn->executeQuery($sqlString, $parameters, $types);
    $data = $statement->fetchAll();
    if (!is_null($callback) && count($data) > 0) {
        $out = [];
        foreach ($data as $row) {
            if (call_user_func_array($callback, [&$row]) !== false) {
                $out[] = $row;
            }
        }
        $data = $out;
   }
 
   return $data;
}

И, наконец, транзакции (я обычно никогда не использую autocommit, и мне нравится обрабатывать транзакции самостоятельно)

$db->transactional(function (Db $db) {
    $userId = 'temporal';
 
    $db->insert('users', [
        'USERID'   => $userId,
        'PASSWORD' => uniqid(),
        'NAME'     => 'name3',
        'SURNAME'  => 'name3'
    ]);
 
    $db->update('users', ['NAME' => 'updatedName'], ['USERID' => $userId]);
    $db->delete('users', ['USERID' => $userId]);
});

«Транзакционная» функция очень похожа на транзакционную функцию DBAL.

public function transactional(\Closure $callback)
{
    $out = null;
    $this->conn->beginTransaction();
    try {
        $out = $callback($this);
        $this->conn->commit();
    } catch (\Exception $e) {
        $this->conn->rollback();
        throw $e;
    }
 
    return $out;
}

Я немного изменяюсь, потому что мне нравится возвращать значение в замыкании и разрешать делать такие вещи:

$status = $db->transactional(function (Db $db) {
    $userId = 'temporal';
 
    $db->insert('users', [
        'USERID'   => $userId,
        'PASSWORD' => uniqid(),
        'NAME'     => 'name3',
        'SURNAME'  => 'name3'
    ]);
 
    $db->update('users', ['NAME' => 'updatedName'], ['USERID' => $userId]);
    $db->delete('users', ['USERID' => $userId]);
 
    return "OK"
});

Другие функции (вставка, обновление, удаление) только обходят вызовы функций DBAL:

private $conn;
 
public function __construct(Doctrine\DBAL\Connection $conn)
{
    $this->conn = $conn;
}
 
public function insert($tableName, array $values = [], array $types = [])
{
    $this->conn->insert($tableName, $values, $types);
}
 
public function delete($tableName, array $where = [], array $types = [])
{
    $this->conn->delete($tableName, $where, $types);
}
 
public function update($tableName, array $data, array $where = [], array $types = [])
{
    $this->conn->update($tableName, $data, $where, $types);
}

И это все. Вы можете использовать библиотеку с composer и скачать на github .

Кстати, я протестировал новый продукт Sensiolabs ( SensioLabs Insight ), чтобы проанализировать код и проверить хорошие практики, и я получил платиновую медаль #yeah!