phplibrary/src/Database.php

260 lines
9 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace {
if (!function_exists('sqliteLower')) {
function sqliteLower($str)
{
return mb_strtolower($str, 'UTF-8');
}
}
}
namespace ctiso {
use PDO;
use ctiso\Database\Statement;
use ctiso\Database\PDOStatement;
use ctiso\Database\IdGenerator;
/**
* Класс оболочка для PDO для замены Creole
* @phpstan-type DSN = array{phptype: string, hostspec: string, database: string, username: string, password: string}
*/
class Database extends PDO
{
/** @var DSN */
public $dsn;
/**
* Создает соединение с базой данных
* @param string $dsn - DSN
* @param string|null $username - имя пользователя
* @param string|null $password - пароль
*/
public function __construct($dsn, $username = null, $password = null)
{
parent::__construct($dsn, $username, $password);
$this->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$this->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
$this->setAttribute(PDO::ATTR_STATEMENT_CLASS, [PDOStatement::class, []]);
}
function prepare(string $sql, array $options = []): PDOStatement|false
{
/** @var PDOStatement $result */
$result = parent::prepare($sql, $options);
return $result;
}
/**
* Возвращает DSN
* @return DSN
*/
public function getDSN()
{
return $this->dsn;
}
/**
* Возвращает true, если база данных Postgres
* @return bool
*/
public function isPostgres()
{
return ($this->dsn["phptype"] == "pgsql");
}
/**
* Создает соединение с базой данных
* @param array $dsn - DSN
* @return Database|null
*/
static function getConnection(array $dsn)
{
/** @var ?Database */
$connection = null;
if ($dsn['phptype'] == 'pgsql' || $dsn['phptype'] == 'mysql') {
$port = (isset($dsn['port'])) ? "port={$dsn['port']};" : "";
$connection = new self("{$dsn['phptype']}:host={$dsn['hostspec']}; $port dbname={$dsn['database']}", $dsn['username'], $dsn['password']);
if ($dsn['phptype'] == 'pgsql') {
$connection->query('SET client_encoding="UTF-8"');
}
if (isset($dsn['schema'])) {
$connection->query('SET search_path TO ' . $dsn['schema']);
}
} elseif ($dsn['phptype'] == 'sqlite::memory') {
$connection = new self("{$dsn['phptype']}:");
$connection->sqliteCreateFunction('LOWER', 'sqliteLower', 1);
} elseif ($dsn['phptype'] == 'sqlite') {
$connection = new self("{$dsn['phptype']}:{$dsn['database']}");
$connection->setAttribute(PDO::ATTR_TIMEOUT, 5);
$mode = defined('SQLITE_JOURNAL_MODE') ? \SQLITE_JOURNAL_MODE : 'WAL';
$connection->query("PRAGMA journal_mode=$mode");
$connection->sqliteCreateFunction('LOWER', 'sqliteLower', 1);
}
$connection->dsn = $dsn;
return $connection;
}
/**
* Выполняет запрос к базе данных
* @param string $query - запрос
* @param ?array<string, mixed> $values - значения
*/
public function executeQuery($query, $values = null): PDOStatement|bool
{
$stmt = $this->prepare($query);
$stmt->execute($values);
$stmt->cache = $stmt->fetchAll(PDO::FETCH_ASSOC);
return $stmt;
}
/**
* Создает подготовленный запрос
* @param string $query - запрос
* @return Statement
*/
public function prepareStatement($query)
{
return new Statement($query, $this);
}
/**
* Извлекает из базы все элементы по запросу (Для совместимости со старым представлением баз данных CIS)
* @param string $query - запрос
* @param ?array<string, mixed> $values - значения
* @return list<array<string, mixed>>
*/
public function fetchAllArray($query, $values = null)
{
$sth = $this->prepare($query);
$prep = $this->prepareValues($values);
$sth->execute($prep);
return $sth->fetchAll(PDO::FETCH_ASSOC);
}
/**
* Извлекает из базы первый элемент по запросу
* @param string $query - запрос
* @param ?array<string, mixed> $values - значения
* @return array<string, mixed>|false
*/
public function fetchOneArray($query, $values = null)
{
$sth = $this->prepare($query);
$prep = $this->prepareValues($values);
$sth->execute($prep);
return $sth->fetch(PDO::FETCH_ASSOC);
}
/**
* Преобразует значения в подготовленные значения
* @param array $values - значения
* @return ?array<string, string>
*/
private function prepareValues($values)
{
if (!$values) {
return null;
}
$pg = $this->isPostgres();
$prep = [];
foreach ($values as $key => $value) {
$result = null;
if (is_bool($value)) {
if ($pg) {
$result = $value ? 'true' : 'false';
} else {
$result = $value ? 1 : 0;
}
} else {
$result = $value;
}
$prep[":" . $key] = $result;
}
return $prep;
}
/**
* Создает INSERT запрос
* @param string $table - таблица
* @param array $values - значения
* @param bool $return_id - возвращать id
* @param string $index - индекс
* @return int|mixed
*/
function insertQuery($table, array $values, $return_id = false, $index = null)
{
$prep = $this->prepareValues($values);
$sql = "INSERT INTO $table (" . implode(",", array_keys($values))
. ") VALUES (" . implode(",", array_keys($prep)) . ")";
if ($return_id) {
if ($this->isPostgres()) {
$sql .= " RETURNING $index";
}
}
$stmt = $this->prepare($sql);
$stmt->setFetchMode(PDO::FETCH_ASSOC);
$stmt->execute($prep);
$result = $stmt->fetch();
if ($return_id) {
if ($this->isPostgres()) {
return $result[$index];
} else {
$result = $this->fetchOneArray("SELECT $index AS lastid FROM $table WHERE OID = last_insert_rowid()");
return $result['lastid'];
}
}
}
/**
* Создает UPDATE запрос
* @param string $table - таблица
* @param array $values - значения
* @param string $cond - условие
*/
function updateQuery($table, array $values, $cond): void
{
$prep = $this->prepareValues($values);
$sql = "UPDATE $table SET " . implode(
",",
array_map(function ($k, $v) {
return $k . "=" . $v; }, array_keys($values), array_keys($prep))
) . " WHERE $cond";
$stmt = $this->prepare($sql);
$stmt->setFetchMode(PDO::FETCH_ASSOC);
$stmt->execute($prep);
}
/**
* Создает генератор идентификаторов
* @return IdGenerator
*/
function getIdGenerator()
{
return new IdGenerator($this);
}
/**
* Замечание: Только для Postgres SQL
* @param string $seq Имя последовательности для ключа таблицы
* @return int Идентефикатор следующей записи
*/
function getNextId($seq)
{
$result = $this->fetchOneArray("SELECT nextval('$seq')");
return $result['nextval'];
}
/**
* Закрывает соединение с базой данных
*/
function close(): void
{
}
}
}