From b73b0c0d3e27f2864b35dc17d32afabb595b29c4 Mon Sep 17 00:00:00 2001 From: "Bruno O. Notario" Date: Thu, 25 Jan 2024 00:47:54 -0300 Subject: [PATCH] expand db driver logic --- system/database/databases/mssql.php | 41 +++++--- system/database/databases/mysql_legacy.php | 20 ++++ system/database/databases/nullStatement.php | 13 +++ system/database/databases/oracle.php | 62 ++++++++++- system/database/databases/postgre.php | 58 ++++++++++- system/database/databases/sqlite3_db.php | 56 ++++++++++ system/database/databases/sqlsrv.php | 47 +++++++++ system/database/databases/sqlsrvpdo.php | 108 +++++++++++++++----- 8 files changed, 365 insertions(+), 40 deletions(-) diff --git a/system/database/databases/mssql.php b/system/database/databases/mssql.php index 5e800a0..f270a01 100644 --- a/system/database/databases/mssql.php +++ b/system/database/databases/mssql.php @@ -12,6 +12,7 @@ namespace Phacil\Framework\Databases; * Legacy class to connect a MS SQL Server with PHP 5 legacy driver. * * Doesn't work with PHP 7+ + * @deprecated 2.0.0 * @package Phacil\Framework\Databases */ final class MSSQL implements \Phacil\Framework\Interfaces\Databases { @@ -19,22 +20,22 @@ final class MSSQL implements \Phacil\Framework\Interfaces\Databases public function __construct($hostname, $username, $password, $database, $port = '1443', $charset = 'utf8') { - if (!$this->connection = mssql_connect($hostname, $username, $password)) { + if (!$this->connection = \mssql_connect($hostname, $username, $password)) { throw new \Phacil\Framework\Exception('Error: Could not make a database connection using ' . $username . '@' . $hostname); } - if (!mssql_select_db($database, $this->connection)) { + if (!\mssql_select_db($database, $this->connection)) { throw new \Phacil\Framework\Exception('Error: Could not connect to database ' . $database); } - mssql_query("SET NAMES 'utf8'", $this->connection); - mssql_query("SET CHARACTER SET utf8", $this->connection); - mssql_query("SET CHARACTER_SET_CONNECTION=utf8", $this->connection); + \mssql_query("SET NAMES 'utf8'", $this->connection); + \mssql_query("SET CHARACTER SET utf8", $this->connection); + \mssql_query("SET CHARACTER_SET_CONNECTION=utf8", $this->connection); } public function query($sql) { - $resource = mssql_query($sql, $this->connection); + $resource = \mssql_query($sql, $this->connection); if ($resource) { if (is_resource($resource)) { @@ -42,13 +43,13 @@ final class MSSQL implements \Phacil\Framework\Interfaces\Databases $data = array(); - while ($result = mssql_fetch_assoc($resource)) { + while ($result = \mssql_fetch_assoc($resource)) { $data[$i] = $result; $i++; } - mssql_free_result($resource); + \mssql_free_result($resource); $query = new \Phacil\Framework\Databases\Object\Result(); $query->row = isset($data[0]) ? $data[0] : array(); @@ -68,25 +69,25 @@ final class MSSQL implements \Phacil\Framework\Interfaces\Databases public function escape($value) { - return mssql_real_escape_string($value, $this->connection); + return \mssql_real_escape_string($value, $this->connection); } public function countAffected() { - return mssql_rows_affected($this->connection); + return \mssql_rows_affected($this->connection); } public function getLastId() { $last_id = false; - $resource = mssql_query("SELECT @@identity AS id", $this->connection); + $resource = \mssql_query("SELECT @@identity AS id", $this->connection); if ($row = mssql_fetch_row($resource)) { $last_id = trim($row[0]); } - mssql_free_result($resource); + \mssql_free_result($resource); return $last_id; } @@ -97,6 +98,20 @@ final class MSSQL implements \Phacil\Framework\Interfaces\Databases public function __destruct() { - mssql_close($this->connection); + \mssql_close($this->connection); + } + + /** + * Execute a prepared statement with parameters + * + * @param string $sql SQL query with named placeholders + * @param array $params Associative array of parameters + * @return \Phacil\Framework\Databases\Object\ResultInterface|true + * @throws \Phacil\Framework\Exception + */ + public function execute($sql, array $params = []) + { + $sql = str_replace(array_keys($params), array_values($params), $sql); + return $this->query($sql); } } diff --git a/system/database/databases/mysql_legacy.php b/system/database/databases/mysql_legacy.php index 95db79e..213a4ec 100644 --- a/system/database/databases/mysql_legacy.php +++ b/system/database/databases/mysql_legacy.php @@ -88,4 +88,24 @@ final class MySQL_legacy implements \Phacil\Framework\Interfaces\Databases { public function __destruct() { \mysql_close($this->connection); } + + /** + * Execute a prepared statement with parameters + * + * @param string $sql SQL query with named placeholders + * @param array $params Associative array of parameters + * @return \Phacil\Framework\Databases\Object\ResultInterface|true + * @throws \Phacil\Framework\Exception + */ + public function execute($sql, array $params = []) + { + foreach ($params as $placeholder => &$param) { + $bindParams[] = $this->escape($param); + + if (is_string($placeholder)) + $sql = str_replace($placeholder, $this->escape($param), $sql); + } + $sql = str_replace(array_keys($params), array_values($bindParams), $sql); + return $this->query($sql); + } } diff --git a/system/database/databases/nullStatement.php b/system/database/databases/nullStatement.php index 22524d9..2b421a4 100644 --- a/system/database/databases/nullStatement.php +++ b/system/database/databases/nullStatement.php @@ -55,4 +55,17 @@ final class nullStatement implements Databases { public function __destruct() { return NULL; } + + /** + * Execute a prepared statement with parameters + * + * @param string $sql SQL query with named placeholders + * @param array $params Associative array of parameters + * @return null + * @throws \Phacil\Framework\Exception + */ + public function execute($sql, array $params = []) + { + return [null]; + } } diff --git a/system/database/databases/oracle.php b/system/database/databases/oracle.php index aa30d80..154981e 100644 --- a/system/database/databases/oracle.php +++ b/system/database/databases/oracle.php @@ -15,7 +15,7 @@ use Phacil\Framework\Interfaces\Databases; * * @package Phacil\Framework\Databases */ -final class Oracle implements Databases { +class Oracle implements Databases { /** * * @var resource|false @@ -95,4 +95,64 @@ final class Oracle implements Databases { public function __destruct() { oci_close($this->connection); } + + /** + * Execute a prepared statement with parameters + * + * @param string $sql SQL query with named placeholders + * @param array $params Associative array of parameters + * @return \Phacil\Framework\Databases\Object\ResultInterface|true + * @throws \Phacil\Framework\Exception + */ + public function execute($sql, array $params = []) + { + // Verificar se há parâmetros e fazer o bind + if (!empty($params)) { + $sql = $this->replacePlaceholders($sql, array_keys($params)); + + $stid = oci_parse($this->connection, $sql); + + foreach ($params as $placeholder => &$param) { + oci_bind_by_name($stid, $placeholder, $param); + } + + $result_exec = oci_execute($stid); + + if ($result_exec === false) { + $e = oci_error($stid); + throw new \Phacil\Framework\Exception('Error executing query: ' . htmlentities($e['message'], ENT_QUOTES)); + } + + // Processar resultados se for um SELECT + $res = []; + oci_fetch_all($stid, $res); + + $resultObj = new \Phacil\Framework\Databases\Object\Result(); + $resultObj->setNumRows(oci_num_rows($stid)); + $resultObj->setRow(isset($res[0]) ? $res[0] : []); + $resultObj->setRows($res); + + return $resultObj; + } else { + // Se não há parâmetros, executar diretamente sem consulta preparada + $query = $this->query($sql); + return $query; + } + } + + /** + * Replace placeholders in the SQL query with named placeholders + * + * @param string $sql SQL query with named placeholders + * @param array $placeholders Array of named placeholders + * @return string SQL query with named placeholders + */ + private function replacePlaceholders($sql, $placeholders) + { + foreach ($placeholders as $placeholder) { + $sql = str_replace($placeholder, ':' . $placeholder, $sql); + } + + return $sql; + } } \ No newline at end of file diff --git a/system/database/databases/postgre.php b/system/database/databases/postgre.php index 62f386c..2538b2f 100644 --- a/system/database/databases/postgre.php +++ b/system/database/databases/postgre.php @@ -10,7 +10,7 @@ namespace Phacil\Framework\Databases; use Phacil\Framework\Interfaces\Databases; -final class Postgre implements Databases { +class Postgre implements Databases { /** * * @var resource|false @@ -99,4 +99,60 @@ final class Postgre implements Databases { public function __destruct() { pg_close($this->link); } + + + /** + * Execute a prepared statement with parameters + * + * @param string $sql SQL query with named placeholders + * @param array $params Associative array of parameters + * @return \Phacil\Framework\Databases\Object\ResultInterface|true + * @throws \Phacil\Framework\Exception + */ + public function execute($sql, array $params = []) + { + // Verificar se há parâmetros e fazer o bind + if (!empty($params)) { + $sql = $this->replacePlaceholders($sql, array_keys($params)); + $result = pg_query_params($this->link, $sql, array_values($params)); + + if ($result === false) { + throw new \Phacil\Framework\Exception('Error executing query: ' . pg_last_error($this->link)); + } + + // Processar resultados se for um SELECT + $i = 0; + $data = []; + while ($row = pg_fetch_assoc($result)) { + $data[$i] = $row; + $i++; + } + + $resultObj = new \Phacil\Framework\Databases\Object\Result(); + $resultObj->setNumRows($i); + $resultObj->setRow(isset($data[0]) ? $data[0] : []); + $resultObj->setRows($data); + + return $resultObj; + } else { + // Se não há parâmetros, executar diretamente sem consulta preparada + return $this->query($sql); + } + } + + /** + * Replace placeholders in the SQL query with named placeholders + * + * @param string $sql SQL query with named placeholders + * @param array $placeholders Array of named placeholders + * @return string SQL query with named placeholders + */ + private function replacePlaceholders($sql, $placeholders) + { + foreach ($placeholders as $placeholder) { + $sql = str_replace($placeholder, '$' . ($placeholders[$placeholder] + 1), $sql); + } + + return $sql; + } } \ No newline at end of file diff --git a/system/database/databases/sqlite3_db.php b/system/database/databases/sqlite3_db.php index 492c136..f643c79 100644 --- a/system/database/databases/sqlite3_db.php +++ b/system/database/databases/sqlite3_db.php @@ -100,4 +100,60 @@ final class Sqlite3_db implements Databases { public function __destruct() { $this->connection->close(); } + + + /** + * Execute a prepared statement with parameters + * + * @param string $sql SQL query with named placeholders + * @param array $params Associative array of parameters + * @return \Phacil\Framework\Databases\Object\ResultInterface|true + * @throws \Phacil\Framework\Exception + */ + public function execute($sql, array $params = []) + { + if (!empty($params)) { + // Bind parameters if there are any + foreach ($params as $placeholder => &$param) { + $sql = str_replace($placeholder, ':' . $placeholder, $sql); + } + + $stmt = $this->connection->prepare($sql); + + if ($stmt === false) { + throw new \Phacil\Framework\Exception('Error preparing query: ' . $this->connection->lastErrorMsg()); + } + + foreach ($params as $placeholder => &$param) { + $stmt->bindValue(':' . $placeholder, $param); + } + + $result = $stmt->execute(); + + if ($result === false) { + throw new \Phacil\Framework\Exception('Error executing query: ' . $this->connection->lastErrorMsg()); + } + + // Processar resultados se for um SELECT + if ($result instanceof \SQLite3Result) { + $data = []; + while ($row = $result->fetchArray(SQLITE3_ASSOC)) { + $data[] = $row; + } + + $resultObj = new \Phacil\Framework\Databases\Object\Result(); + $resultObj->setNumRows(count($data)); + $resultObj->setRow(isset($data[0]) ? $data[0] : []); + $resultObj->setRows($data); + + return $resultObj; + } + + // Se não for um SELECT, apenas retornar verdadeiro + return true; + } else { + // Se não há parâmetros, executar diretamente sem consulta preparada + return $this->query($sql); + } + } } \ No newline at end of file diff --git a/system/database/databases/sqlsrv.php b/system/database/databases/sqlsrv.php index 5cde809..2e3d49f 100644 --- a/system/database/databases/sqlsrv.php +++ b/system/database/databases/sqlsrv.php @@ -127,4 +127,51 @@ final class SQLSRV implements Databases { public function __destruct() { \sqlsrv_close($this->link); } + + /** + * Execute a prepared statement with parameters + * + * @param string $sql SQL query with named placeholders + * @param array $params Associative array of parameters + * @return \Phacil\Framework\Databases\Object\ResultInterface|true + * @throws \Phacil\Framework\Exception + */ + public function execute($sql, array $params = []) + { + if (!empty($params)) { + // Bind parameters if there are any + $stmt = \sqlsrv_prepare($this->link, $sql, $params); + + if ($stmt === false) { + throw new \Phacil\Framework\Exception('Error preparing query: ' . \sqlsrv_errors()); + } + + $result = \sqlsrv_execute($stmt); + + if ($result === false) { + throw new \Phacil\Framework\Exception('Error executing query: ' . \sqlsrv_errors()); + } + + // Processar resultados se for um SELECT + if (\sqlsrv_has_rows($stmt)) { + $data = []; + while ($row = \sqlsrv_fetch_array($stmt, \SQLSRV_FETCH_ASSOC)) { + $data[] = $row; + } + + $resultObj = new \Phacil\Framework\Databases\Object\Result(); + $resultObj->setNumRows(\sqlsrv_num_rows($stmt)); + $resultObj->setRow(isset($data[0]) ? $data[0] : []); + $resultObj->setRows($data); + + return $resultObj; + } + + // Se não for um SELECT, apenas retornar verdadeiro + return true; + } else { + // Se não há parâmetros, executar diretamente sem consulta preparada + return $this->query($sql); + } + } } \ No newline at end of file diff --git a/system/database/databases/sqlsrvpdo.php b/system/database/databases/sqlsrvpdo.php index 280cb8a..e9c0de0 100644 --- a/system/database/databases/sqlsrvpdo.php +++ b/system/database/databases/sqlsrvpdo.php @@ -20,10 +20,16 @@ final class sqlsrvPDO implements Databases { /** * - * @var PDONative + * @var PDOStatement */ private $statement = null; + /** + * + * @var int + */ + private $affectedRows = 0; + /** * * @param string $hostname @@ -68,29 +74,6 @@ final class sqlsrvPDO implements Databases { } } - /** - * @return never - * @throws Exception - */ - public function execute() { - try { - if ($this->statement && $this->statement->execute()) { - $data = array(); - - while ($row = $this->statement->fetch(\PDO::FETCH_ASSOC)) { - $data[] = $row; - } - - $result = new \stdClass(); - $result->row = (isset($data[0])) ? $data[0] : array(); - $result->rows = $data; - $result->num_rows = $this->statement->rowCount(); - } - } catch(\PDOException $e) { - throw new \Exception('Error: ' . $e->getMessage() . ' Error Code : ' . $e->getCode()); - } - } - /** * * @param string $sql @@ -144,7 +127,7 @@ final class sqlsrvPDO implements Databases { if ($this->statement) { return $this->statement->rowCount(); } else { - return 0; + return $this->affectedRows; } } @@ -166,4 +149,79 @@ final class sqlsrvPDO implements Databases { public function __destruct() { unset($this->connection); } + + /** + * Execute a prepared statement with parameters + * + * @param string $sql SQL query with named placeholders + * @param array $params Associative array of parameters + * @return \Phacil\Framework\Databases\Object\ResultInterface|true + * @throws \Phacil\Framework\Exception + */ + public function execute($sql, array $params = []) + { + try { + $this->statement = $this->connection->prepare($sql); + + if ($this->statement === false) { + throw new \Phacil\Framework\Exception('Error preparing query: ' . implode(', ', $this->connection->errorInfo())); + } + + // Bind parameters if there are any + if (!empty($params)) { + foreach ($params as $placeholder => &$param) { + $this->statement->bindParam($placeholder, $param, $this->getParamType($param)); + } + } + + $this->statement->execute(); + + if ($this->statement->columnCount()) { + $data = new \Phacil\Framework\Databases\Object\Result(); + $data->setNumRows($this->statement->rowCount()); + $data->setRows($this->statement->fetchAll(\PDO::FETCH_ASSOC)); + $this->statement->closeCursor(); + + return $data; + } else { + $this->affectedRows = $this->statement->rowCount(); + $this->statement->closeCursor(); + + return true; + } + } catch (\PDOException $exception) { + throw new \Phacil\Framework\Exception($exception->getMessage()); + } + } + + /** + * + * @param mixed $param + * @return int + */ + private function getParamType(&$param) + { + $paramType = gettype($param); + + switch ($paramType) { + case 'boolean': + $paramType = \PDO::PARAM_BOOL; + break; + case 'integer': + $paramType = \PDO::PARAM_INT; + break; + case 'double': + case 'float': + $paramType = \PDO::PARAM_STR; + break; + case 'NULL': + $paramType = \PDO::PARAM_NULL; + break; + default: + $paramType = \PDO::PARAM_STR; + break; + } + + return $paramType; + } } \ No newline at end of file