From 6d4f7fc44b5817f74315d1f8ee78337c9230002c Mon Sep 17 00:00:00 2001 From: Victor Westerlund Date: Mon, 16 Oct 2023 12:59:43 +0200 Subject: [PATCH] feat: add ORDER BY and LIMIT OFFSET methods --- src/DatabaseDriver.php | 121 +++++++++++++++++++++++++++++++++++ src/MySQL.php | 142 ++++++++++------------------------------- 2 files changed, 155 insertions(+), 108 deletions(-) create mode 100644 src/DatabaseDriver.php diff --git a/src/DatabaseDriver.php b/src/DatabaseDriver.php new file mode 100644 index 0000000..6bc72ed --- /dev/null +++ b/src/DatabaseDriver.php @@ -0,0 +1,121 @@ +bind_param($types, ...$params); + } + + // Execute an SQL query with a prepared statement + private function run_query(string $sql, mixed $params = null): mysqli_result|bool { + $stmt = $this->prepare($sql); + + // Bind parameters if provided + if ($params !== null) { + $this->bind_params($stmt, $params); + } + + // Execute statement and get retrieve changes + $query = $stmt->execute(); + $res = $stmt->get_result(); + + // Return true if an INSERT, UPDATE or DELETE was sucessful (no rows returned) + if (!empty($query) && empty($res)) { + return true; + } + + // Return mysqli_result object + return $res; + } + + /* ---- */ + + // Execute SQL query with optional prepared statement and return array of affected rows + public function exec(string $sql, mixed $params = null): array { + $query = $this->run_query($sql, $params); + $res = []; + + // Fetch rows into sequential array + while ($row = $query->fetch_assoc()) { + $res[] = $row; + } + + return $res; + } + + // Execute SQL query with optional prepared statement and return true if query was successful + public function exec_bool(string $sql, mixed $params = null): bool { + $query = $this->run_query($sql, $params); + + return gettype($query) === "boolean" + // Type is already a bool, so return it as is + ? $query + // Return true if rows were matched + : $query->num_rows > 0; + } + } diff --git a/src/MySQL.php b/src/MySQL.php index ee92189..4f51b87 100644 --- a/src/MySQL.php +++ b/src/MySQL.php @@ -2,22 +2,17 @@ namespace libmysqldriver; - use \mysqli; - use \mysqli_stmt; - use \mysqli_result; + use libmysqldriver\Driver\DatabaseDriver; - // Streamline common MariaDB operations for LAMSdb3 - class MySQL extends mysqli { - // Passing arguments to https://www.php.net/manual/en/mysqli.construct.php + require_once "DatabaseDriver.php"; + + // Interface for MySQL_Driver with abstractions for data manipulation + class MySQL extends DatabaseDriver { + // Pass constructor arguments to driver function __construct() { parent::__construct(...func_get_args()); } - // Create CSV from array - private static function csv(array $items): string { - return implode(",", $items); - } - // Create WHERE AND clause from assoc array of "column" => "value" private static function where(?array $filter = null): array { // Return array of an empty string and empty array if no filter is defined @@ -29,126 +24,57 @@ $stmt = array_map(fn($k): string => "`{$k}` = ?", array_keys($filter)); // Separate each filter with ANDs - $sql = "WHERE " . implode(" AND ", $stmt); + $sql = " WHERE " . implode(" AND ", $stmt); // Return array of SQL prepared statement string and values return [$sql, array_values($filter)]; } - /* ---- */ + // Return SQL SORT BY string from assoc array of columns and direction + private static function order_by(array $order_by): string { + $sql = " ORDER BY "; - // Create CSV from columns - public static function columns(array|string $columns): string { - return is_array($columns) - ? self::csv($columns) - : $columns; + // Create CSV from columns + $sql .= implode(",", array_keys($order_by)); + // Create pipe DSV from values + $sql .= " " . implode("|", array_values($order_by)); + + return $sql; } - // Return CSV of '?' for use with prepared statements - public static function values(array|string $values): string { - return is_array($values) - ? self::csv(array_fill(0, count($values), "?")) - : "?"; - } + // Return SQL LIMIT string from integer or array of [offset => limit] + private static function limit(int|array $limit): string { + $sql = " LIMIT "; - /* ---- */ - - // Bind SQL statements - private function bind_params(mysqli_stmt &$stmt, mixed $params): bool { - // Convert single value parameter to array - $params = is_array($params) ? $params : [$params]; - if (empty($params)) { - return true; + // Return LIMIT without range directly as string + if (is_int($limit)) { + return $sql . $limit; } - // Concatenated string with types for each parameter - $types = ""; + // Use array key as LIMIT range start value + $offset = (int) array_keys($limit)[0]; + // Use array value as LIMIT range end value + $limit = (int) array_values($limit)[0]; - // Convert PHP primitves to SQL primitives - foreach ($params as $param) { - switch (gettype($param)) { - case "integer": - case "double": - case "boolean": - $types .= "i"; - break; - - case "string": - case "array": - case "object": - $types .= "s"; - break; - - default: - $types .= "b"; - break; - } - } - - return $stmt->bind_param($types, ...$params); - } - - // Execute an SQL query with a prepared statement - private function run_query(string $sql, mixed $params = null): mysqli_result|bool { - $stmt = $this->prepare($sql); - - // Bind parameters if provided - if ($params !== null) { - $this->bind_params($stmt, $params); - } - - // Execute statement and get retrieve changes - $query = $stmt->execute(); - $res = $stmt->get_result(); - - // Return true if an INSERT, UPDATE or DELETE was sucessful (no rows returned) - if (!empty($query) && empty($res)) { - return true; - } - - // Return mysqli_result object - return $res; - } - - /* ---- */ - - // Execute SQL query with optional prepared statement and return array of affected rows - public function exec(string $sql, mixed $params = null): array { - $query = $this->run_query($sql, $params); - $res = []; - - // Fetch rows into sequential array - while ($row = $query->fetch_assoc()) { - $res[] = $row; - } - - return $res; - } - - // Execute SQL query with optional prepared statement and return true if query was successful - private function exec_bool(string $sql, mixed $params = null): bool { - $query = $this->run_query($sql, $params); - - return gettype($query) === "boolean" - // Type is already a bool, so return it as is - ? $query - // Return true if rows were matched - : $query->num_rows > 0; + // Return as SQL LIMIT CSV + return $sql . "{$offset},{$limit}"; } /* ---- */ // Create Prepared Statament for SELECT with optional WHERE filters - public function get(string $table, array|string $columns = null, ?array $filter = [], ?int $limit = null): array|bool { - // Create CSV string of columns + public function get(string $table, array|string $columns = null, ?array $filter = [], ?array $order_by = null, int|array|null $limit = null): array|bool { + // Create CSV string of columns if argument defined, else return bool $columns_sql = $columns ? self::columns($columns) : "NULL"; // Create LIMIT statement if argument is defined - $limit_sql = is_int($limit) ? "LIMIT {$limit}" : ""; + $limit_sql = $limit ? self::limit($limit) : ""; + // Create ORDER BY statement if argument is defined + $order_by_sql = $order_by ? self::order_by($order_by) : ""; // Get array of SQL WHERE string and filter values [$filter_sql, $filter_values] = self::where($filter); // Interpolate components into an SQL SELECT statmenet and execute - $sql = "SELECT {$columns_sql} FROM {$table} {$filter_sql} {$limit_sql}"; + $sql = "SELECT {$columns_sql} FROM {$table}{$filter_sql}{$order_by_sql}{$limit_sql}"; // No columns were specified, return true if query matched rows if (!$columns) {