nulib/php/src/db/pdo/Pdo.php

269 lines
7.2 KiB
PHP

<?php
namespace nulib\db\pdo;
use Generator;
use nulib\cl;
use nulib\db\_private\_config;
use nulib\db\_private\Tvalues;
use nulib\db\IDatabase;
use nulib\db\ITransactor;
use nulib\php\func;
use nulib\ValueException;
class Pdo implements IDatabase {
use Tvalues;
static function with($pdo, ?array $params=null): self {
if ($pdo instanceof static) {
return $pdo;
} elseif ($pdo instanceof self) {
# recréer avec les mêmes paramètres
return new static(null, cl::merge([
"dbconn" => $pdo->dbconn,
"options" => $pdo->options,
"config" => $pdo->config,
"migration" => $pdo->migration,
], $params));
} else {
return new static($pdo, $params);
}
}
static function config_errmodeException_lowerCase(self $pdo): void {
$pdo->db->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
$pdo->db->setAttribute(\PDO::ATTR_CASE, \PDO::CASE_LOWER);
}
const CONFIG_errmodeException_lowerCase = [self::class, "config_errmodeException_lowerCase"];
static function config_unbufferedQueries(self $pdo): void {
$pdo->db->setAttribute(\PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false);
}
const CONFIG_unbufferedQueries = [self::class, "config_unbufferedQueries"];
protected const OPTIONS = [
\PDO::ATTR_PERSISTENT => true,
];
protected const DEFAULT_CONFIG = [
self::CONFIG_errmodeException_lowerCase,
];
protected const CONFIG = null;
protected const MIGRATION = null;
const dbconn_SCHEMA = [
"name" => "string",
"user" => "?string",
"pass" => "?string",
];
const params_SCHEMA = [
"dbconn" => ["array"],
"options" => ["?array|callable"],
"replace_config" => ["?array|callable"],
"config" => ["?array|callable"],
"migration" => ["?array|string|callable"],
"auto_open" => ["bool", true],
];
function __construct($dbconn=null, ?array $params=null) {
if ($dbconn !== null) {
if (!is_array($dbconn)) {
$dbconn = ["name" => $dbconn];
#XXX à terme, il faudra interroger config
#$tmp = config::db($dbconn);
#if ($tmp !== null) $dbconn = $tmp;
#else $dbconn = ["name" => $dbconn];
}
$params["dbconn"] = $dbconn;
}
# dbconn
$this->dbconn = $params["dbconn"] ?? null;
$this->dbconn["name"] ??= null;
$this->dbconn["user"] ??= null;
$this->dbconn["pass"] ??= null;
# options
$this->options = $params["options"] ?? static::OPTIONS;
# configuration
$config = $params["replace_config"] ?? null;
if ($config === null) {
$config = $params["config"] ?? static::CONFIG;
if (is_callable($config)) $config = [$config];
$config = cl::merge(static::DEFAULT_CONFIG, $config);
}
$this->config = $config;
# migrations
$this->migration = $params["migration"] ?? static::MIGRATION;
#
$defaultAutoOpen = self::params_SCHEMA["auto_open"][1];
if ($params["auto_open"] ?? $defaultAutoOpen) {
$this->open();
}
}
protected ?array $dbconn;
/** @var array|callable */
protected $options;
/** @var array|string|callable */
protected $config;
/** @var array|string|callable */
protected $migration;
protected ?\PDO $db = null;
function open(): self {
if ($this->db === null) {
$dbconn = $this->dbconn;
$options = $this->options;
if (is_callable($options)) {
$options = func::with($options)->bind($this, true)->invoke();
}
$this->db = new \PDO($dbconn["name"], $dbconn["user"], $dbconn["pass"], $options);
_config::with($this->config)->configure($this);
//_migration::with($this->migration)->migrate($this);
}
return $this;
}
function close(): void {
$this->db = null;
}
protected function db(): \PDO {
$this->open();
return $this->db;
}
/** @return int|false */
function _exec(string $query) {
return $this->db()->exec($query);
}
function exec($query, ?array $params=null) {
$db = $this->db();
$query = new _pdoQuery($query, $params);
if ($query->_use_stmt($db, $stmt, $sql)) {
if ($stmt->execute() === false) return false;
if ($query->isInsert()) return $db->lastInsertId();
else return $stmt->rowCount();
} else {
$rowCount = $db->exec($sql);
if ($query->isInsert()) return $db->lastInsertId();
else return $rowCount;
}
}
/** @var ITransactor[] */
protected ?array $transactors = null;
function willUpdate(...$transactors): self {
foreach ($transactors as $transactor) {
if ($transactor instanceof ITransactor) {
$this->transactors[] = $transactor;
$transactor->willUpdate();
} else {
throw ValueException::invalid_type($transactor, ITransactor::class);
}
}
return $this;
}
function inTransaction(): bool {
return $this->db()->inTransaction();
}
function beginTransaction(?callable $func=null, bool $commit=true): void {
$this->db()->beginTransaction();
if ($this->transactors !== null) {
foreach ($this->transactors as $transactor) {
$transactor->beginTransaction();
}
}
if ($func !== null) {
$commited = false;
try {
func::call($func, $this);
if ($commit) {
$this->commit();
$commited = true;
}
} finally {
if ($commit && !$commited) $this->rollback();
}
}
}
function commit(): void {
$this->db()->commit();
if ($this->transactors !== null) {
foreach ($this->transactors as $transactor) {
$transactor->commit();
}
}
}
function rollback(): void {
$this->db()->rollBack();
if ($this->transactors !== null) {
foreach ($this->transactors as $transactor) {
$transactor->rollback();
}
}
}
function get($query, ?array $params=null, bool $entireRow=false) {
$db = $this->db();
$query = new _pdoQuery($query, $params);
$stmt = null;
try {
/** @var \PDOStatement $stmt */
if ($query->_use_stmt($db, $stmt, $sql)) {
if ($stmt->execute() === false) return null;
} else {
$stmt = $db->query($sql);
}
$row = $stmt->fetch(\PDO::FETCH_ASSOC);
if ($row === false) return null;
$this->verifixRow($row);
if ($entireRow) return $row;
else return cl::first($row);
} finally {
if ($stmt instanceof \PDOStatement) $stmt->closeCursor();
}
}
function one($query, ?array $params=null): ?array {
return $this->get($query, $params, true);
}
function all($query, ?array $params=null, $primaryKeys=null): iterable {
$db = $this->db();
$query = new _pdoQuery($query, $params);
$stmt = null;
try {
/** @var \PDOStatement $stmt */
if ($query->_use_stmt($db, $stmt, $sql)) {
if ($stmt->execute() === false) return;
} else {
$stmt = $db->query($sql);
}
$primaryKeys = cl::withn($primaryKeys);
while (($row = $stmt->fetch(\PDO::FETCH_ASSOC)) !== false) {
$this->verifixRow($row);
if ($primaryKeys !== null) {
$key = implode("-", cl::select($row, $primaryKeys));
yield $key => $row;
} else {
yield $row;
}
}
} finally {
if ($stmt instanceof \PDOStatement) $stmt->closeCursor();
}
}
}