原生 PHP 小型 API 如何规范变量命名、路由和 HTTP 方法处理?
结论
小型 API 可以用原生 PHP 实现:由一个入口文件匹配路径,再根据 HTTP 方法分派请求。当前代码适合作为起点,但需要先修复 $requestPath 未定义的问题,并补齐路由未命中、方法不支持和 JSON 响应的处理。
变量命名总体容易理解,主要问题是同一个变量先后表示不同数据,以及 $Body 的大小写风格不一致。
当前代码有哪些具体问题?
1. 路径解析使用了未定义变量
原代码先把路径保存到 $uri,下一行却使用了 $requestPath:
$uri = parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH);
$uri = explode("/", $requestPath);
这段代码没有定义 $requestPath,因此会触发未定义变量诊断,路径也无法按预期拆分。具体表现取决于 PHP 版本和错误处理配置。
如果需要拆分路径,可以分别命名:
$requestPath = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
$pathSegments = explode('/', trim($requestPath, '/'));
如果项目只有 /topics 这样的少数固定路由,直接比较完整路径即可,无需先拆成数组。
2. 只匹配最后一段路径,范围过宽
switch (end($uri)) 只检查路径的最后一段。即使修复了前面的变量问题,/topics 和 /anything/topics 仍可能进入同一个分支,而 /topics/ 的最后一段为空,无法匹配。
建议明确规定可访问的路径,再决定是否把末尾 / 视为等价形式。对于简单项目,精确匹配 /topics 最容易理解。
3. 未匹配路由没有明确响应
原代码没有 default 分支,遇到未知路径时,代码本身不会明确返回 404 Not Found。
路径存在但不支持请求方法时,应返回 405 Method Not Allowed,并通过 Allow 响应头列出该路径支持的方法。路径不存在时则应返回 404。
4. 请求体解析与当前接口无关
当前分支只查询主题列表,没有使用 $Body,因此可以删除:
$Body = json_decode(file_get_contents("php://input"), true);
以后添加需要 JSON 请求体的接口时,再由对应的处理逻辑读取、解析和校验请求体,以便区分 JSON 语法错误和字段校验错误。
变量名应该如何调整?
可以采用一致的 camelCase 风格,让名称对应实际保存的数据:
| 原变量 | 建议 | 原因 |
|---|---|---|
$method | $requestMethod | 明确表示 HTTP 请求方法 |
$uri | $requestPath | parse_url(..., PHP_URL_PATH) 提取的是路径 |
拆分后的 $uri | $pathSegments | 明确表示路径片段数组 |
$Body | $requestBody | 统一大小写,并说明数据用途 |
$sql、$topics | 可以保留 | 在当前上下文中已经清楚 |
变量名不必一味加长,但应避免让 $uri 先表示字符串,后来又表示数组。
一个适合小型项目的实现示例
下面的示例假设公开路径是 /topics,且 getData($sql) 返回可编码为 JSON 的查询结果。如果项目部署在子目录或带有 API 前缀,需要相应调整路径匹配。
示例使用 JSON_THROW_ON_ERROR,要求 PHP 7.3 或更高版本;同时支持 GET 和 HEAD,其中 HEAD 不输出响应体。
<?php
function respondJson($status, $data, $headOnly = false)
{
$json = json_encode(
$data,
JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR
);
http_response_code($status);
header('Content-Type: application/json; charset=utf-8');
if (!$headOnly) {
echo $json;
}
exit;
}
$requestMethod = $_SERVER['REQUEST_METHOD'];
$isHead = $requestMethod === 'HEAD';
try {
$requestPath = parse_url(
$_SERVER['REQUEST_URI'],
PHP_URL_PATH
);
if ($requestPath === false || $requestPath === null) {
respondJson(400, ['error' => 'Invalid request path'], $isHead);
}
if ($requestPath !== '/topics') {
respondJson(404, ['error' => 'Not found'], $isHead);
}
if (!in_array($requestMethod, ['GET', 'HEAD'], true)) {
header('Allow: GET, HEAD');
respondJson(405, ['error' => 'Method not allowed']);
}
require_once __DIR__ . '/db_functions.php';
$sql = 'SELECT id, name FROM topic';
$topics = getData($sql);
respondJson(200, $topics, $isHead);
} catch (Throwable $e) {
error_log($e->getMessage());
respondJson(500, ['error' => 'Internal server error'], $isHead);
}
示例先匹配路径,再检查方法,因此未知路径返回 404,已知路径上的不支持方法返回 405。JSON 编码在发送状态码和响应体之前完成,编码失败时可以进入异常处理。
require_once __DIR__ . '/db_functions.php' 让依赖路径以当前文件所在目录为基准。纯 PHP 文件可以省略结尾的 ?>,减少意外输出空白的机会。
路由增多后如何组织?
只有几个固定接口时,用 if 或 switch 都可以。入口文件可以按以下顺序处理请求:
- 提取请求方法和路径。
- 匹配完整路径,未匹配时返回
404。 - 检查该路径允许的方法,不支持时返回
405和Allow。 - 调用对应的处理函数。
- 统一生成 JSON 响应。
接口增多后,可以把数据库查询和业务逻辑移到独立函数或类中,让入口文件只负责分派请求。出现大量路径参数、嵌套路由或重复的鉴权逻辑时,再考虑引入路由库。
使用时还需要注意什么?
- Web 服务器需要把
/topics请求交给这个入口文件。仅编写 PHP 路由代码,不会自动完成 URL 重写配置。 - 示例没有提供
getData()的实现。它应返回查询数据,并以调用方能够识别的方式报告失败;如果它只返回false,外层catch不会自动把这次查询视为异常。 - 数据库辅助文件不要直接输出调试信息,否则会破坏 JSON 响应。生产环境应记录内部错误,避免把异常详情直接显示给客户端。
- 当前 SQL 是固定字符串。以后加入客户端提供的筛选条件时,应使用预处理语句和参数绑定。
- 示例将
/topics/视为不同路径。如果需要兼容末尾斜杠,应明确选择统一匹配或重定向策略。
备注:内容仅供参考。
