$pgsql->dbconn, "options" => $pgsql->options, "config" => $pgsql->config, "migration" => $pgsql->migration, ], $params)); } else { return new static($pgsql, $params); } } protected const OPTIONS = [ "persistent" => true, "force_new" => false, "serial_support" => true, ]; const CONFIG = null; const MIGRATION = null; const params_SCHEMA = [ "dbconn" => ["array"], "options" => ["?array|callable"], "replace_config" => ["?array|callable"], "config" => ["?array|callable"], "migration" => ["?array|string|callable"], "auto_open" => ["bool", true], ]; const dbconn_SCHEMA = [ "" => "?string", "host" => "string", "hostaddr" => "?string", "port" => "?int", "dbname" => "string", "user" => "string", "password" => "string", "connect_timeout" => "?int", "options" => "?string", "sslmode" => "?string", "service" => "?string", ]; protected const dbconn_MAP = [ "name" => "dbname", "pass" => "password", ]; const options_SCHEMA = [ "persistent" => ["bool", self::OPTIONS["persistent"]], "force_new" => ["bool", self::OPTIONS["force_new"]], ]; function __construct($dbconn=null, ?array $params=null) { if ($dbconn !== null) { if (!is_array($dbconn)) { $dbconn = ["" => $dbconn]; #XXX à terme, il faudra interroger config #$tmp = config::db($dbconn); #if ($tmp !== null) $dbconn = $tmp; #else $dbconn = ["" => $dbconn]; } $params["dbconn"] = $dbconn; } # dbconn $this->dbconn = $params["dbconn"] ?? 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]; } $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|null */ protected $options; /** @var array|string|callable */ protected $config; /** @var array|string|callable */ protected $migration; /** @var resource */ protected $db = null; function open(): self { if ($this->db === null) { $dbconn = $this->dbconn; $connection_string = [$dbconn[""] ?? null]; unset($dbconn[""]); foreach ($dbconn as $key => $value) { if ($value === null) continue; $value = strval($value); if ($value === "" || preg_match("/[ '\\\\]/", $value)) { $value = str_replace("\\", "\\\\", $value); $value = str_replace("'", "\\'", $value); $value = "'$value'"; } $key = cl::get(self::dbconn_MAP, $key, $key); $connection_string[] = "$key=$value"; } $connection_string = implode(" ", array_filter($connection_string)); $options = $this->options; if (is_callable($options)) { $options = func::with($options)->bind($this, true)->invoke(); } $forceNew = $options["force_new"] ?? false; $flags = $forceNew? PGSQL_CONNECT_FORCE_NEW: 0; if ($options["persistent"] ?? true) $db = pg_pconnect($connection_string, $flags); else $db = pg_connect($connection_string, $flags); if ($db === false) throw new PgsqlException("unable to connect"); $this->db = $db; _config::with($this->config)->configure($this); //_migration::with($this->migration)->migrate($this); } return $this; } function close(): self { if ($this->db !== null) { pg_close($this->db); $this->db = null; } return $this; } protected function db() { $this->open(); return $this->db; } function _exec(string $query): bool { $result = pg_query($this->db(), $query); if ($result === false) return false; pg_free_result($result); return true; } function getLastSerial() { $db = $this->db(); $result = @pg_query($db, "select lastval()"); if ($result === false) return false; $lastSerial = pg_fetch_row($result)[0]; pg_free_result($result); return $lastSerial; } function exec($query, ?array $params=null) { $db = $this->db(); $query = new _pgsqlQuery($query, $params); $result = $query->_exec($db); $serialSupport = $this->options["serial_support"] ?? true; if ($serialSupport && $query->isInsert()) return $this->getLastSerial(); $affected_rows = pg_affected_rows($result); pg_free_result($result); return $affected_rows; } /** @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 &$inerror=null): bool { $status = pg_transaction_status($this->db()); if ($status === PGSQL_TRANSACTION_ACTIVE || $status === PGSQL_TRANSACTION_INTRANS) { $inerror = false; return true; } elseif ($status === PGSQL_TRANSACTION_INERROR) { $inerror = true; return true; } else { return false; } } function beginTransaction(?callable $func=null, bool $commit=true): void { $this->_exec("begin"); 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->_exec("commit"); if ($this->transactors !== null) { foreach ($this->transactors as $transactor) { $transactor->commit(); } } } function rollback(): void { $this->_exec("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 _pgsqlQuery($query, $params); $result = $query->_exec($db); $row = pg_fetch_assoc($result); pg_free_result($result); if ($row === false) return null; $this->verifixRow($row); if ($entireRow) return $row; else return cl::first($row); } 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 _pgsqlQuery($query, $params); $result = $query->_exec($db); $primaryKeys = cl::withn($primaryKeys); while (($row = pg_fetch_assoc($result)) !== false) { $this->verifixRow($row); if ($primaryKeys !== null) { $key = implode("-", cl::select($row, $primaryKeys)); yield $key => $row; } else { yield $row; } } pg_free_result($result); } }