285 lines
		
	
	
		
			7.4 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			285 lines
		
	
	
		
			7.4 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
| <?php
 | |
| namespace nulib\db\pdo;
 | |
| 
 | |
| 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;
 | |
| 
 | |
|   const PREFIX = null;
 | |
| 
 | |
|   function getPrefix(): ?string {
 | |
|     return static::PREFIX;
 | |
|   }
 | |
| 
 | |
|   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"];
 | |
| 
 | |
|   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 getSql($query, ?array $params=null): string {
 | |
|     $query = new _pdoQuery($query, $params);
 | |
|     return $query->getSql();
 | |
|   }
 | |
| 
 | |
|   function open(bool $reopen=false): self {
 | |
|     if ($this->db === null || $reopen) {
 | |
|       $dbconn = $this->dbconn;
 | |
|       $options = $this->options;
 | |
|       if (is_callable($options)) {
 | |
|         $options = func::with($options)->bind($this)->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;
 | |
|   }
 | |
| 
 | |
|   const SQL_CHECK_LIVE = "select 1";
 | |
| 
 | |
|   function ensure(): self {
 | |
|     try {
 | |
|       $this->_exec(static::SQL_CHECK_LIVE);
 | |
|     } catch (\PDOException $e) {
 | |
|       $this->open(true);
 | |
|     }
 | |
|     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();
 | |
|     }
 | |
|   }
 | |
| }
 |