歡迎來到《21天學會PHP 8.4》的第十九天!在昨天,我們學會了如何安全地操作數據庫。今天,我們將回到 Web 開發的核心——HTTP 協議,並使用純原生 PHP(無框架) 構建一個符合 RESTful 風格的 API。這不僅能加深你對 Web 請求/響應機制的理解,還能讓你在使用 Laravel、Symfony 等框架時“知其所以然”。

今日目標

  1. 理解 HTTP 方法(GET/POST/PUT/DELETE)與狀態碼
  2. 使用 $_SERVERphp://input 獲取請求數據
  3. 構建路由分發器(Router)處理不同 URL 和方法
  4. 返回 JSON 響應並設置正確 Content-Type 與狀態碼
  5. 處理 CORS(跨域資源共享)以支持前端調用
  6. 實現一個完整的用户管理 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


今日小練習

  1. 擴展路由系統,支持動態路徑(如 /users/{id}),並實現完整的 CRUD。
  2. 添加輸入驗證(郵箱格式、必填字段),返回 400 錯誤。
  3. (挑戰)為 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開發 #編程入門