setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $pdo->setAttribute(PDO::ATTR_CASE, PDO::CASE_LOWER); } function __construct($dbname, ?string $dbuser=null, ?string $dbpass=null) { if (is_array($dbname)) { if ($dbuser === null) $dbuser = $dbname["user"]; if ($dbpass === null) $dbpass = $dbname["pass"]; $dbname = $dbname["name"]; } $this->beforeInitPdo($options); $this->dbname = $dbname; $this->dbuser = $dbuser; $this->dbpass = $dbpass; $this->options = $options; try { $pdo = new PDO($dbname, $dbuser, $dbpass, $options); } catch (PDOException $e) { throw new QueryException("unable to create PDO instance", $e); } $this->afterInitPdo($pdo); $this->pdo = $pdo; } function __destruct() { $this->rollback(); } function getInfos(): array { return [$this->dbname, $this->dbuser, $this->dbpass, $this->options]; } function beginTransaction(): void { if (!$this->inTransaction) { try { $this->pdo->beginTransaction(); } catch (PDOException $e) { throw new QueryException("cannot begin transaction", $e); } $this->inTransaction = true; } } function commit(): void { if ($this->inTransaction) { $this->inTransaction = false; try { $this->pdo->commit(); } catch (PDOException $e) { throw new QueryException("cannot commit", $e); } } } function rollback(): void { if ($this->inTransaction) { $this->inTransaction = false; try { $this->pdo->rollBack(); } catch (PDOException $e) { throw new QueryException("cannot rollback", $e); } } } private function bindParam(PDOStatement $stmt, string $name, &$value): void { try { if (is_array($value)) { # tableau associatif: on peut spécifier la longueur et le type de la valeur $maxlength = A::get($value, "maxlength", -1); $type = A::get($value, "type", PDO::PARAM_STR); $stmt->bindParam($name, $value["value"], $type, $maxlength); } else { # sinon, faire un bind simple $stmt->bindParam($name, $value); } } catch (PDOException $e) { throw new QueryException("error binding with $name", $e); } } function _execute0(string $sql, ?array &$bindings, array $params): array { if ($params["transaction"]) $this->beginTransaction(); try { $stmt = $this->pdo->prepare($sql); } catch (PDOException $e) { throw new QueryException("preparation error", $e); } if ($bindings !== null) { foreach ($bindings as $name => &$values) { # IMPORTANT: il faut binder sur l'adresse de $values if (A::is_seq($values)) { # liste de valeurs: binder avec des noms générés incrémentalement # de la forme $name_$index $count = count($values); for ($index = 0; $index < $count; $index++) { $this->bindParam($stmt, "${name}_${index}", $values[$index]); } } else { # une seule valeur $this->bindParam($stmt, $name, $values); } }; unset($values); } try { $stmt->execute(); } catch (PDOException $e) { throw new QueryException("execute error", $e); } $r = []; if ($params["num_rows"]) { $r["num_rows"] = $stmt->rowCount(); } if ($params["last_insert_id"]) { $r["insert_id"] = $this->HAVE_LAST_INSERT_ID()? $this->pdo->lastInsertId(): null; } if ($params["stmt"]) { $r["stmt"] = $stmt; } else { $stmt->closeCursor(); } return $r; } function _prepareLogger(string $sql, ?array $bindings): array { $queryLogger = $this->queryLogger; $actualQuery = null; $traceSql = config::k("trace_sql", false); if ($traceSql || $queryLogger !== null) { $actualQuery = PdoQuery::build_actual_query($sql, $bindings); } if ($traceSql) debug::log("SQL TRACE --", $actualQuery); return [$queryLogger, $actualQuery]; } function _execute1(string $sql, ?array &$bindings, array $params): array { [$queryLogger, $actualQuery] = $this->_prepareLogger($sql, $bindings); $r = $this->_execute0($sql, $bindings, $params); if ($queryLogger !== null) $queryLogger->logQuery($actualQuery); return $r; } function _execute(string $sql, ?array &$bindings=null, ?array $params=null): array { md::ensure_schema($params, self::EXECUTE_PARAMS_SCHEMA); return $this->_execute1($sql, $bindings, $params); } function _fetchAll(string $sql, ?array &$bindings=null): array { ["stmt" => $stmt ] = $this->_execute1($sql, $bindings, self::EXECUTE_PARAMS_DQL); try { $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); } catch (PDOException $e) { throw new QueryException("fetch error", $e); } finally { $stmt->closeCursor(); } return $rows; } function _fetchFirst(string $sql, ?array &$bindings=null): ?array { ["stmt" => $stmt ] = $this->_execute1($sql, $bindings, self::EXECUTE_PARAMS_DQL); try { $row = $stmt->fetch(PDO::FETCH_ASSOC); } catch (PDOException $e) { throw new QueryException("fetch error", $e); } finally { $stmt->closeCursor(); } return $row !== false? $row: null; } function _update(string $sql, ?array &$bindings=null): int { ["num_rows" => $numRows ] = $this->_execute1($sql, $bindings, self::EXECUTE_PARAMS_DML_UPDATE); return $numRows; } function query(?string $sql=null, ?array $filter=null, ?IRowIncarnation $incarnation=null): IQuery { return new PdoQuery($this, $sql, $filter, $incarnation); } }