Refine DB Helpers

This commit is contained in:
johnnyq 2025-12-10 18:32:46 -05:00
parent 84cc4a094a
commit b27ffe6635
1 changed files with 43 additions and 46 deletions

View File

@ -1808,7 +1808,6 @@ function dbExecute(mysqli $mysqli, string $sql, array $params = []): mysqli_stmt
$values = []; $values = [];
foreach ($params as $param) { foreach ($params as $param) {
// Detect type
if (is_int($param)) { if (is_int($param)) {
$types .= 'i'; $types .= 'i';
} elseif (is_float($param)) { } elseif (is_float($param)) {
@ -1817,17 +1816,14 @@ function dbExecute(mysqli $mysqli, string $sql, array $params = []): mysqli_stmt
$types .= 'i'; $types .= 'i';
$param = $param ? 1 : 0; $param = $param ? 1 : 0;
} elseif (is_null($param)) { } elseif (is_null($param)) {
// bind as string, MySQL will accept NULL when using proper SQL (e.g. "col = ?")
$types .= 's'; $types .= 's';
$param = null; $param = null;
} else { } else {
$types .= 's'; $types .= 's';
} }
$values[] = $param; $values[] = $param;
} }
// bind_param needs references; unpack will pass them correctly
if (!$stmt->bind_param($types, ...$values)) { if (!$stmt->bind_param($types, ...$values)) {
throw new Exception('MySQLi bind_param error: ' . $stmt->error . ' | SQL: ' . $sql); throw new Exception('MySQLi bind_param error: ' . $stmt->error . ' | SQL: ' . $sql);
} }
@ -1847,11 +1843,9 @@ function dbFetchAll(mysqli $mysqli, string $sql, array $params = []): array
{ {
$stmt = dbExecute($mysqli, $sql, $params); $stmt = dbExecute($mysqli, $sql, $params);
$result = $stmt->get_result(); $result = $stmt->get_result();
if ($result === false) { if ($result === false) {
return []; return [];
} }
return $result->fetch_all(MYSQLI_ASSOC); return $result->fetch_all(MYSQLI_ASSOC);
} }
@ -1862,13 +1856,10 @@ function dbFetchOne(mysqli $mysqli, string $sql, array $params = []): ?array
{ {
$stmt = dbExecute($mysqli, $sql, $params); $stmt = dbExecute($mysqli, $sql, $params);
$result = $stmt->get_result(); $result = $stmt->get_result();
if ($result === false) { if ($result === false) {
return null; return null;
} }
$row = $result->fetch_assoc(); $row = $result->fetch_assoc();
return $row !== null ? $row : null; return $row !== null ? $row : null;
} }
@ -1881,14 +1872,13 @@ function dbFetchValue(mysqli $mysqli, string $sql, array $params = [])
if ($row === null) { if ($row === null) {
return null; return null;
} }
return reset($row); return reset($row);
} }
/** /**
* INSERT using "SET" style. * INSERT using "SET" style.
* Example: * Example:
* $id = dbInsertSet($mysqli, 'clients', [ * $id = dbInsert($mysqli, 'clients', [
* 'client_name' => $name, * 'client_name' => $name,
* 'client_type' => $type, * 'client_type' => $type,
* ]); * ]);
@ -1898,13 +1888,12 @@ function dbFetchValue(mysqli $mysqli, string $sql, array $params = [])
* @throws InvalidArgumentException * @throws InvalidArgumentException
* @throws Exception * @throws Exception
*/ */
function dbInsertSet(mysqli $mysqli, string $table, array $data): int function dbInsert(mysqli $mysqli, string $table, array $data): int
{ {
if (empty($data)) { if (empty($data)) {
throw new InvalidArgumentException('dbInsertSet called with empty $data'); throw new InvalidArgumentException('dbInsert called with empty $data');
} }
// NOTE: $table and keys must NOT come from untrusted user input
$setParts = []; $setParts = [];
foreach ($data as $column => $_) { foreach ($data as $column => $_) {
$setParts[] = "$column = ?"; $setParts[] = "$column = ?";
@ -1918,72 +1907,80 @@ function dbInsertSet(mysqli $mysqli, string $table, array $data): int
return $mysqli->insert_id; return $mysqli->insert_id;
} }
/** function dbUpdate(
* UPDATE using "SET" style and a WHERE clause with placeholders.
*
* Example:
* dbUpdateSet(
* $mysqli,
* 'clients',
* ['client_name' => $name, 'client_type' => $type],
* 'client_id = ?',
* [$client_id]
* );
*
* @return int affected_rows
*
* @throws InvalidArgumentException
* @throws Exception
*/
function dbUpdateSet(
mysqli $mysqli, mysqli $mysqli,
string $table, string $table,
array $data, array $data,
string $whereSql, $where,
array $whereParams = [] array $whereParams = []
): int { ): int {
if (empty($data)) { if (empty($data)) {
throw new InvalidArgumentException('dbUpdateSet called with empty $data'); throw new InvalidArgumentException('dbUpdate called with empty $data');
}
if (empty($where)) {
throw new InvalidArgumentException('dbUpdate requires a WHERE clause');
} }
if (trim($whereSql) === '') {
throw new InvalidArgumentException('dbUpdateSet requires a WHERE clause');
}
// Again, $table and keys must not come from user input
$setParts = []; $setParts = [];
foreach ($data as $column => $_) { foreach ($data as $column => $_) {
$setParts[] = "$column = ?"; $setParts[] = "$column = ?";
} }
if (is_array($where)) {
$whereParts = [];
$whereParams = [];
foreach ($where as $column => $value) {
$whereParts[] = "$column = ?";
$whereParams[] = $value;
}
$whereSql = implode(' AND ', $whereParts);
} else {
$whereSql = $where;
}
$sql = "UPDATE $table SET " . implode(', ', $setParts) . " WHERE $whereSql"; $sql = "UPDATE $table SET " . implode(', ', $setParts) . " WHERE $whereSql";
$params = array_merge(array_values($data), $whereParams); $params = array_merge(array_values($data), $whereParams);
$stmt = dbExecute($mysqli, $sql, $params); $stmt = dbExecute($mysqli, $sql, $params);
return $stmt->affected_rows; return $stmt->affected_rows;
} }
/** /**
* DELETE helper. * DELETE helper.
* *
* Example: * WHERE can be:
* dbDelete($mysqli, 'client_tags', 'client_id = ?', [$client_id]); * - array: ['client_id' => $id] (auto "client_id = ?")
* - string: 'client_id = ?' (use with $whereParams)
* *
* @return int affected_rows * @return int affected_rows
* *
* @throws InvalidArgumentException * @throws InvalidArgumentException
* @throws Exception * @throws Exception
*/ */
function dbDelete(mysqli $mysqli, string $table, string $whereSql, array $whereParams = []): int function dbDelete(
{ mysqli $mysqli,
if (trim($whereSql) === '') { string $table,
$where,
array $whereParams = []
): int {
if (empty($where)) {
throw new InvalidArgumentException('dbDelete requires a WHERE clause'); throw new InvalidArgumentException('dbDelete requires a WHERE clause');
} }
if (is_array($where)) {
$whereParts = [];
$whereParams = [];
foreach ($where as $column => $value) {
$whereParts[] = "$column = ?";
$whereParams[] = $value;
}
$whereSql = implode(' AND ', $whereParts);
} else {
$whereSql = $where;
}
$sql = "DELETE FROM $table WHERE $whereSql"; $sql = "DELETE FROM $table WHERE $whereSql";
$stmt = dbExecute($mysqli, $sql, $whereParams); $stmt = dbExecute($mysqli, $sql, $whereParams);
return $stmt->affected_rows; return $stmt->affected_rows;
} }