242 lines
		
	
	
		
			6.5 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			242 lines
		
	
	
		
			6.5 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
<?php
 | 
						|
namespace nur\sery\db\pdo;
 | 
						|
 | 
						|
use Generator;
 | 
						|
use nur\sery\cl;
 | 
						|
use nur\sery\db\IDatabase;
 | 
						|
use nur\sery\php\func;
 | 
						|
use nur\sery\php\time\Date;
 | 
						|
use nur\sery\php\time\DateTime;
 | 
						|
 | 
						|
class Pdo implements IDatabase {
 | 
						|
  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,
 | 
						|
        "migrate" => $pdo->migration,
 | 
						|
      ], $params));
 | 
						|
    } else {
 | 
						|
      return new static($pdo, $params);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  static function config_errmodeException_lowerCase(self $pdo) {
 | 
						|
    $pdo->db->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
 | 
						|
    $pdo->db->setAttribute(\PDO::ATTR_CASE, \PDO::CASE_LOWER);
 | 
						|
  }
 | 
						|
 | 
						|
  const OPTIONS = [
 | 
						|
    \PDO::ATTR_PERSISTENT => true,
 | 
						|
  ];
 | 
						|
 | 
						|
  const CONFIG = [
 | 
						|
    [self::class, "config_errmodeException_lowerCase"],
 | 
						|
  ];
 | 
						|
 | 
						|
  const MIGRATE = null;
 | 
						|
 | 
						|
  const dbconn_SCHEMA = [
 | 
						|
    "name" => "string",
 | 
						|
    "user" => "?string",
 | 
						|
    "pass" => "?string",
 | 
						|
  ];
 | 
						|
 | 
						|
  const params_SCHEMA = [
 | 
						|
    "dbconn" => ["array"],
 | 
						|
    "options" => ["?array|callable"],
 | 
						|
    "config" => ["?array|callable"],
 | 
						|
    "migrate" => ["?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
 | 
						|
    $this->config = $params["config"] ?? static::CONFIG;
 | 
						|
    # migrations
 | 
						|
    $this->migration = $params["migrate"] ?? static::MIGRATE;
 | 
						|
    #
 | 
						|
    $defaultAutoOpen = self::params_SCHEMA["auto_open"][1];
 | 
						|
    if ($params["auto_open"] ?? $defaultAutoOpen) {
 | 
						|
      $this->open();
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  protected ?array $dbconn;
 | 
						|
 | 
						|
  /** @var array|callable */
 | 
						|
  protected array $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)) {
 | 
						|
        func::ensure_func($options, $this, $args);
 | 
						|
        $options = func::call($options, ...$args);
 | 
						|
      }
 | 
						|
      $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);
 | 
						|
  }
 | 
						|
 | 
						|
  private static function is_insert(?string $sql): bool {
 | 
						|
    if ($sql === null) return false;
 | 
						|
    return preg_match('/^\s*insert\b/i', $sql);
 | 
						|
  }
 | 
						|
 | 
						|
  function exec($query, ?array $params=null) {
 | 
						|
    $db = $this->db();
 | 
						|
    $query = new _query_base($query, $params);
 | 
						|
    if ($query->useStmt($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 (self::is_insert($sql)) return $db->lastInsertId();
 | 
						|
      else return $rowCount;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  function beginTransaction(): void {
 | 
						|
    $this->db()->beginTransaction();
 | 
						|
  }
 | 
						|
 | 
						|
  function commit(): void {
 | 
						|
    $this->db()->commit();
 | 
						|
  }
 | 
						|
 | 
						|
  function rollback(): void {
 | 
						|
    $this->db()->rollBack();
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Tester si $date est une date/heure valide de la forme "YYYY-mm-dd HH:MM:SS"
 | 
						|
   *
 | 
						|
   * Si oui, $ms obtient les 6 éléments de la chaine
 | 
						|
   */
 | 
						|
  static function is_datetime($date, ?array &$ms=null): bool {
 | 
						|
    return is_string($date) && preg_match('/^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})$/', $date, $ms);
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Tester si $date est une date valide de la forme "YYYY-mm-dd [00:00:00]"
 | 
						|
   *
 | 
						|
   * Si oui, $ms obtient les 3 éléments de la chaine
 | 
						|
   */
 | 
						|
  static function is_date($date, ?array &$ms=null): bool {
 | 
						|
    return is_string($date) && preg_match('/^(\d{4})-(\d{2})-(\d{2})(?: 00:00:00)?$/', $date, $ms);
 | 
						|
  }
 | 
						|
 | 
						|
  function verifixRow(array &$row) {
 | 
						|
    foreach ($row as &$value) {
 | 
						|
      if (self::is_date($value)) {
 | 
						|
        $value = new Date($value);
 | 
						|
      } elseif (self::is_date($value)) {
 | 
						|
        $value = new DateTime($value);
 | 
						|
      }
 | 
						|
    }; unset($value);
 | 
						|
  }
 | 
						|
 | 
						|
  function get($query, ?array $params=null, bool $entireRow=false) {
 | 
						|
    $db = $this->db();
 | 
						|
    $query = new _query_base($query, $params);
 | 
						|
    $stmt = null;
 | 
						|
    try {
 | 
						|
      /** @var \PDOStatement $stmt */
 | 
						|
      if ($query->useStmt($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);
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * si $primaryKeys est fourni, le résultat est indexé sur la(es) colonne(s)
 | 
						|
   * spécifiée(s)
 | 
						|
   */
 | 
						|
  function all($query, ?array $params=null, $primaryKeys=null): Generator {
 | 
						|
    $db = $this->db();
 | 
						|
    $query = new _query_base($query, $params);
 | 
						|
    $stmt = null;
 | 
						|
    try {
 | 
						|
      /** @var \PDOStatement $stmt */
 | 
						|
      if ($query->useStmt($db, $stmt, $sql)) {
 | 
						|
        if ($stmt->execute() === false) return;
 | 
						|
      } else {
 | 
						|
        $stmt = $db->query($sql);
 | 
						|
      }
 | 
						|
      if ($primaryKeys !== null) $primaryKeys = cl::with($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();
 | 
						|
    }
 | 
						|
  }
 | 
						|
}
 |