REST API 风格
JSON 响应、状态码;对照 Spring Boot。
30 分钟
REST API 风格
这一课你将能做到
- 按 REST 语义设计资源路由:URL 定资源、方法定动作、状态码定结果
- 用 PHP 原生实现一个任务清单(Todo)API:GET/POST/PUT/DELETE 全覆盖
- 统一 JSON 响应结构与错误处理,并用 curl 对每个接口做验收测试
为什么要学
REST 是 HTTP 世界的「通用普通话」:前端 Vue/React、小程序、第三方系统、开放平台,全都通过「资源 + 方法 + 状态码」这套语义交互。PHP 上一课已经会用 PDO 操作数据,这一课把数据能力包成规范接口,你就打通了「前端请求 → 路由分发 → 业务处理 → 数据落库 → 统一响应」的完整链路——这正是 Laravel/Symfony 框架做的事,只是框架帮你写了路由器和分发器。
人话:REST 就是「把数据库里的东西当资源,用 HTTP 方法当动词」:GET 读、POST 建、PUT 全量改、DELETE 删;状态码 200/201/204/400/404/422/500 各说各话,别一律 200。
先建立图像
任务清单 API 的路由表(本课要实现的全部接口):
GET /api/tasks 列出任务(支持 ?status=done 过滤) 200
POST /api/tasks 新建任务(请求体 JSON) 201
GET /api/tasks/{id} 查询单个任务 200 / 404
PUT /api/tasks/{id} 全量更新任务 200 / 404
DELETE /api/tasks/{id} 删除任务 204 / 404
约定:
URL 只有名词复数,动词交给 HTTP 方法
状态码表达结果,响应体统一 {"code": ..., "message": ..., "data": ...}
跟着做
步骤 1:路由分发器(前端控制器)
api.php——所有请求的唯一入口:
<?php
declare(strict_types=1);
header('Content-Type: application/json; charset=utf-8');
header('Access-Control-Allow-Origin: *'); // 演示用 CORS
function respond(int $code, array $data = []): never {
http_response_code($code);
if ($code !== 204) { // 204 无响应体
echo json_encode($data + ['code' => $code], JSON_UNESCAPED_UNICODE);
}
exit;
}
$method = $_SERVER['REQUEST_METHOD'];
$uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH); // /api/tasks/3
// 从 /api/tasks/3 提取资源与 id
if (!str_starts_with($uri, '/api/tasks')) {
respond(404, ['message' => '未知资源']);
}
$id = null;
if (preg_match('#^/api/tasks/(\d+)$#', $uri, $m)) {
$id = (int) $m[1];
}
内置服务器支持路由脚本,让所有路径都进 api.php:php -S localhost:8000 api.php。
步骤 2:GET 列表与详情
<?php
// 沿用上一课的 $pdo 连接(真实代码 require db.php)
if ($method === 'GET' && $id === null) {
$status = $_GET['status'] ?? null; // 过滤参数走查询串
$sql = 'SELECT id, title, status, created_at FROM tasks';
$params = [];
if ($status !== null) {
$sql .= ' WHERE status = ?';
$params[] = $status;
}
$sql .= ' ORDER BY id DESC LIMIT 20';
$stmt = $pdo->prepare($sql);
$stmt->execute($params);
respond(200, ['data' => $stmt->fetchAll()]);
}
if ($method === 'GET' && $id !== null) {
$stmt = $pdo->prepare('SELECT * FROM tasks WHERE id = ?');
$stmt->execute([$id]);
$task = $stmt->fetch();
$task === false
? respond(404, ['message' => "任务 {$id} 不存在"])
: respond(200, ['data' => $task]);
}