diff --git a/README.md b/README.md index 043df77..db074f0 100644 --- a/README.md +++ b/README.md @@ -43,8 +43,10 @@ $db->get( array|string $columns, // (Optional) key, value array of column names and values to filter with WHERE = ?array $filter = null, + // (Optional) order by columns name => direction ("ASC"|"DESC") + ?array $order_by = null, // (Optional) max number of rows to return - ?int $limit = null + int|array|null $limit = null ): array|bool; // Returns array of arrays for each row, or bool if $columns = null ``` @@ -93,15 +95,40 @@ $coffee = $db->get("beverages", $columns, $filter); ] ``` -## LIMIT +## ORDER BY -You can also pass an optional integer as the 4:th argument to `MySQL->get()` and `LIMIT` the rows to match - -> **Note** -> Passing (int) `1` will flatten the returned array to two dimensions (k => v) +You can also pass an associative array as the 4:th argument to `MySQL->get()` to `ORDER BY` a column or multiple columns ```php -$coffee = $db->get("beverages", $columns, $filter, 1); +$coffee = $db->get("beverages", $columns, $filter, ["beverage_name" => "ASC"], 2); +// SELECT beverage_name, beverage_size FROM beverages ORDER BY beverage_name ASC LIMIT 2 +``` +```php +[ + [ + "beverage_name" => "tea", + "beverage_size" => 10 + ], + [ + "beverage_name" => "tea", + "beverage_size" => 15 + ], + // ...etc for "beverage_name = coffee" +] +``` + +## LIMIT + +You can also pass an optional integer or associative array as the 5:th argument to `MySQL->get()` and `LIMIT` the rows to match. + +> **Note** +> Passing (int) `1` will flatten the returned array from `get()` to two dimensions (k => v) + +### Passing an integer to LIMIT +This will simply `LIMIT` the results returned to the integer passed + +```php +$coffee = $db->get("beverages", $columns, $filter, null, 1); // SELECT beverage_name, beverage_size FROM beverages WHERE beverage_type = "coffee" LIMIT 1 ``` ```php @@ -111,6 +138,27 @@ $coffee = $db->get("beverages", $columns, $filter, 1); ] ``` +### Passing an associative array to LIMIT +This will `OFFSET` and `LIMIT` the results returned from the first key of the array as `OFFSET` and the value of that key as `LIMIT` + +```php +$coffee = $db->get("beverages", $columns, $filter, null, [3 => 2]); +// SELECT beverage_name, beverage_size FROM beverages LIMIT 3 OFFSET 2 +``` +```php +[ + [ + "beverage_name" => "tea", + "beverage_size" => 10 + ], + [ + "beverage_name" => "tea", + "beverage_size" => 15 + ], + // ...etc +] +``` + # INSERT Use `MySQL->insert()` to append a new row to a database table 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) {