歡迎來到《21天學會PHP 8.4》的第十九天!在昨天,我們學會了如何安全地操作數據庫。今天,我們將回到 Web 開發的核心——HTTP 協議,並使用純原生 PHP(無框架) 構建一個符合 RESTful 風格的 API。這不僅能加深你對 Web 請求/響應機制的理解,還能讓你在使用 Laravel、Symfony 等框架時“知其所以然”。
今日目標
- 理解 HTTP 方法(GET/POST/PUT/DELETE)與狀態碼
- 使用
$_SERVER、php://input獲取請求數據 - 構建路由分發器(Router)處理不同 URL 和方法
- 返回 JSON 響應並設置正確 Content-Type 與狀態碼
- 處理 CORS(跨域資源共享)以支持前端調用
- 實現一個完整的用户管理 REST API
一、HTTP 基礎回顧
1. 常見 HTTP 方法(動詞)
| 方法 | 用途 | 冪等性 | 安全性 |
|---|---|---|---|
| GET | 獲取資源 | ✅ 是 | ✅ 安全(不應修改數據) |
| POST | 創建資源 / 提交數據 | ❌ 否 | ❌ 不安全 |
| PUT | 完整更新資源 | ✅ 是 | ❌ 不安全 |
| PATCH | 部分更新資源 | ❌ 否 | ❌ 不安全 |
| DELETE | 刪除資源 | ✅ 是 | ❌ 不安全 |
🔑 RESTful 設計原則:URL 表示資源,HTTP 方法表示操作。
2. 常用 HTTP 狀態碼
| 狀態碼 | 含義 | 場景 |
|---|---|---|
200 OK |
成功 | GET/PUT/PATCH 成功 |
201 Created |
創建成功 | POST 成功 |
204 No Content |
成功但無返回體 | DELETE 成功 |
400 Bad Request |
請求格式錯誤 | 缺少參數、JSON 無效 |
404 Not Found |
資源不存在 | ID 不存在 |
405 Method Not Allowed |
方法不支持 | 對 /users 發送 PUT |
500 Internal Server Error |
服務器內部錯誤 | 未捕獲異常 |
二、原生 PHP 處理 HTTP 請求
1. 獲取請求信息
// 請求方法
$method = $_SERVER['REQUEST_METHOD']; // 'GET', 'POST', etc.
// 請求路徑(去除查詢字符串)
$path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
// 獲取 JSON 請求體(用於 POST/PUT)
$input = file_get_contents('php://input');
$data = json_decode($input, true); // 轉為關聯數組
2. 發送 JSON 響應
function jsonResponse(array $data, int $statusCode = 200): void {
http_response_code($statusCode);
header('Content-Type: application/json; charset=utf-8');
echo json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
exit;
}
// 使用
jsonResponse(['message' => 'Hello'], 200);
三、構建簡易路由分發器
<?php
// router.php
class Router {
private array $routes = [];
public function add(string $method, string $path, callable $handler): void {
$this->routes[] = compact('method', 'path', 'handler');
}
public function dispatch(string $requestMethod, string $requestPath): void {
foreach ($this->routes as $route) {
if ($route['method'] === $requestMethod && $route['path'] === $requestPath) {
$input = json_decode(file_get_contents('php://input'), true);
$route['handler']($input);
return;
}
}
http_response_code(404);
echo json_encode(['error' => 'Not Found']);
}
}
💡 此為簡化版;真實項目可使用正則或參數化路由(如
/users/{id}),但今日聚焦核心邏輯。
四、實戰:構建用户管理 REST API
1. 項目結構
rest-api/
├── index.php # 入口文件
├── router.php # 路由器
├── db.php # PDO 連接
└── UserRepository.php # 數據訪問(複用第18天代碼)
2. db.php:數據庫連接
<?php
// db.php
$pdo = new PDO('sqlite:' . __DIR__ . '/data.db', null, null, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]);
// 初始化表(首次運行)
$pdo->exec('CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT NOT NULL UNIQUE
)');
3. UserRepository.php(簡化版)
<?php
// UserRepository.php
class UserRepository {
public function __construct(private PDO $pdo) {}
public function all(): array {
return $this->pdo->query('SELECT * FROM users')->fetchAll();
}
public function find(int $id): ?array {
$stmt = $this->pdo->prepare('SELECT * FROM users WHERE id = ?');
$stmt->execute([$id]);
return $stmt->fetch() ?: null;
}
public function create(string $name, string $email): int {
$stmt = $this->pdo->prepare('INSERT INTO users (name, email) VALUES (?, ?)');
$stmt->execute([$name, $email]);
return $this->pdo->lastInsertId();
}
public function update(int $id, string $name, string $email): bool {
$stmt = $this->pdo->prepare('UPDATE users SET name = ?, email = ? WHERE id = ?');
return (bool) $stmt->execute([$name, $email, $id]);
}
public function delete(int $id): bool {
$stmt = $this->pdo->prepare('DELETE FROM users WHERE id = ?');
return (bool) $stmt->execute([$id]);
}
}
4. index.php:API 入口
<?php
require_once 'db.php';
require_once 'UserRepository.php';
require_once 'router.php';
header('Access-Control-Allow-Origin: *'); // 允許跨域(開發用)
header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type');
// 預檢請求(CORS)
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
exit;
}
$repo = new UserRepository($pdo);
$router = new Router();
// GET /users
$router->add('GET', '/users', function () use ($repo) {
jsonResponse($repo->all());
});
// GET /users/1
$router->add('GET', '/users/1', function () use ($repo) {
$user = $repo->find(1);
if (!$user) {
jsonResponse(['error' => 'User not found'], 404);
}
jsonResponse($user);
});
// POST /users
$router->add('POST', '/users', function (?array $data) use ($repo) {
if (!isset($data['name']) || !isset($data['email'])) {
jsonResponse(['error' => 'Missing name or email'], 400);
}
$id = $repo->create($data['name'], $data['email']);
jsonResponse(['id' => $id], 201);
});
// 為簡潔,PUT/DELETE 略(可自行擴展)
// 分發請求
$path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
$method = $_SERVER['REQUEST_METHOD'];
$router->dispatch($method, $path);
⚠️ 注意:此示例硬編碼了
/users/1,實際應解析路徑參數(如用explode('/', trim($path, '/')))。
五、測試 API
使用 curl 測試
# 獲取所有用户
curl http://localhost/rest-api/users
# 創建用户
curl -X POST http://localhost/rest-api/users \
-H "Content-Type: application/json" \
-d '{"name":"Alice","email":"alice@example.com"}'
# 獲取單個用户(需替換 ID)
curl http://localhost/rest-api/users/1
使用 Postman 或 VS Code REST Client 更直觀!
六、處理 CORS(跨域)
現代前端(React/Vue)通常運行在 http://localhost:3000,而後端在 http://localhost:8000,形成跨域請求。
瀏覽器會先發送 OPTIONS 預檢請求。我們在入口文件中添加:
// 允許所有域(生產環境應指定具體域名!)
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type, Authorization');
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
exit; // 預檢請求無需響應體
}
🔒 生產安全提示:不要使用
*,應設置為你的前端域名,如https://myapp.com。
今日小練習
- 擴展路由系統,支持動態路徑(如
/users/{id}),並實現完整的 CRUD。 - 添加輸入驗證(郵箱格式、必填字段),返回 400 錯誤。
- (挑戰)為 DELETE 操作添加軟刪除(
deleted_at字段),而非物理刪除。
明日預告:第20天 —— 安全基礎:密碼哈希、CSRF 與 XSS 防護
明天我們將學習 Web 應用安全三劍客:
- 使用
password_hash()安全存儲用户密碼 - 防範跨站請求偽造(CSRF)
- 轉義輸出防止跨站腳本攻擊(XSS)
- 安全頭(Security Headers)配置
安全不是功能,而是責任!
結語
通過原生 PHP 構建 REST API,你不僅理解了 HTTP 的本質,也掌握了框架底層的工作原理。這種“從零開始”的能力,是成為高級開發者的關鍵一步。
記住:好的 API = 清晰的資源設計 + 正確的狀態碼 + 一致的錯誤格式。
你離構建完整 Web 應用只差最後幾步。堅持住,勝利在望!
#PHP84 #RESTAPI #HTTP #原生PHP #PDO #CORS #Web開發 #編程入門