PHP mysqli 的查询与数据修改应该共用一个函数吗?
建议保留两个职责明确的函数
建议保留独立的查询函数和数据修改函数,分别返回数据数组和受影响行数,同时共用数据库连接和错误处理策略。
两个函数的实现虽然相似,调用者需要的结果却不同:
SELECT:需要读取结果集,没有记录时返回[]。INSERT/UPDATE/DELETE:通常需要知道语句是否执行成功,以及影响了多少行。
如果项目需要通用执行函数,可以在底层提供,但要明确它可能返回的类型。业务代码通常使用职责清晰的接口更方便。
原代码为什么不能直接兼容两类操作
示例中的查询和修改都使用了 $db->query($sql),差异在后续处理:
$data = ($result->num_rows > 0) ? $result->fetch_all(MYSQLI_ASSOC) : [];
这段代码要求 $result 是 mysqli_result。成功的 SELECT 通常返回结果集,而成功的 INSERT/UPDATE/DELETE 返回 true。布尔值没有 num_rows 属性,也不能调用 fetch_all()。
原代码还把查询结果数组和错误字符串都作为返回值。调用者每次都要判断类型,错误也容易被当成正常数据继续传递。可以统一使用异常处理数据库错误,让正常返回值只表示业务结果。
先复用连接,再拆分返回结果
不必在每个函数中都创建和关闭连接。调用方可以建立连接,再传给查询或修改函数,这样也方便在多个操作之间使用同一个事务。
下面是接口拆分示意。连接参数由应用配置提供,函数中的 $sql 应使用应用内受控的固定 SQL。包含外部输入时,应使用预处理语句。
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$db = new mysqli($host, $user, $password, $database);
$db->set_charset('utf8mb4');
function fetchRows(mysqli $db, string $sql): array
{
$result = $db->query($sql);
if (!($result instanceof mysqli_result)) {
throw new LogicException('Expected a result set.');
}
try {
return $result->fetch_all(MYSQLI_ASSOC);
} finally {
$result->free();
}
}
function executeWrite(mysqli $db, string $sql): int
{
$db->query($sql);
return $db->affected_rows;
}
这里约定 fetchRows() 接收返回结果集的查询,executeWrite() 接收 INSERT/UPDATE/DELETE。示例没有尝试解析 SQL 来判断操作类型。
两个函数的返回值和错误处理分别是:
fetchRows()始终返回数组,空结果返回[]。executeWrite()返回受影响行数,调用方可以自行决定是否要求至少修改一行。- 数据库执行错误通过异常报告,不与正常结果混在一起。
连接由创建它的调用方管理,不在这两个函数中关闭。事务的开始、提交和回滚也应放在能够覆盖完整业务操作的位置。
不要用受影响行数判断执行是否成功
原代码中的这段逻辑会丢失信息:
return $db->affected_rows > 0 ? true : false;
affected_rows 为 0 不一定表示执行失败。例如,UPDATE 可能没有匹配记录,也可能匹配到了记录,但新值与原值相同。具体计数行为还可能受到连接选项影响。
需要区分两个问题:
- SQL 是否执行成功:由执行结果或异常判断。
- 是否影响了记录:由
affected_rows判断。
只有业务明确要求“必须影响至少一行”时,才在业务层判断返回值是否大于 0。
外部参数使用预处理语句
无论最终保留几个函数,都不要把外部输入直接拼接到 SQL。下面以一个假设的 users 表为例:
$stmt = $db->prepare('UPDATE users SET name = ? WHERE id = ?');
$stmt->bind_param('si', $name, $id);
$stmt->execute();
$affectedRows = $stmt->affected_rows;
$stmt->close();
实际封装时,可以为两个接口分别增加参数绑定能力,也可以提取公共的预处理与执行步骤。不必为了减少少量重复,让所有调用者都处理数组、布尔值、整数和错误字符串等混合返回值。
原代码还有一处顺序问题:它先调用 $db->close(),再读取 $db->error。如果采用手动错误处理,应在关闭连接前保存错误信息;采用上面显式开启的异常模式后,可以在统一的异常处理位置记录数据库错误,再向业务层提供合适的错误信息。
备注:内容仅供参考。
