目录

  1. 理解 RESTful 架构

  2. 定义 RESTful 路由

  3. 创建 RESTful 控制器

  4. 更新 Router 以支持 RESTful 请求

  5. 处理 JSON 请求和响应

  6. 状态码和错误处理

  7. 示例:实现用户的 RESTful API

  8. 总结

  9. 进一步扩展建议


理解 RESTful 架构

RESTful 架构 是一种设计风格,旨在构建可扩展和可维护的网络应用。其核心理念包括:

  • 资源(Resources):应用中的实体,如用户、文章等,每个资源通过唯一的 URI 进行标识。

  • HTTP 方法:使用标准的 HTTP 方法(GET、POST、PUT、DELETE)来操作资源。

  • 无状态(Stateless):每个请求都应包含所有必要的信息,服务器不存储客户端的会话信息。

  • 数据格式:通常使用 JSON 作为数据交换格式,易于解析和处理。


定义 RESTful 路由

首先,需要在路由器中定义 RESTful 路由。这些路由通常对应于资源的 CRUD 操作:

  • GET /resources:获取资源列表(Index)

  • GET /resources/{id}:获取单个资源详情(Show)

  • POST /resources:创建新资源(Store)

  • PUT/PATCH /resources/{id}:更新资源(Update)

  • DELETE /resources/{id}:删除资源(Destroy)

示例:定义用户资源的 RESTful 路由

index.php 或路由配置文件中添加以下路由:

<?php
// index.php

use MyFrameworkRouter;
use MyFrameworkControllersUsersController;
use MyFrameworkMiddlewareAuthenticationMiddleware;

// ... 之前的代码 ...

// 定义 RESTful 路由
$router->add('GET', '/users', 'UsersController@index', [AuthenticationMiddleware::class], 'users.index');
$router->add('GET', '/users/{id}', 'UsersController@show', [AuthenticationMiddleware::class], 'users.show');
$router->add('POST', '/users', 'UsersController@store', [AuthenticationMiddleware::class], 'users.store');
$router->add('PUT', '/users/{id}', 'UsersController@update', [AuthenticationMiddleware::class], 'users.update');
$router->add('DELETE', '/users/{id}', 'UsersController@destroy', [AuthenticationMiddleware::class], 'users.destroy');

// 处理请求
$router->dispatch($requestMethod, $requestUri);
?>

说明

  • 每个路由对应一个 HTTP 方法和 URI 模式。

  • 使用命名路由(如 users.index)便于反向路由生成。

  • 大部分操作需要认证中间件保护,确保只有认证用户可以执行。


创建 RESTful 控制器

接下来,需要创建一个控制器来处理这些 RESTful 路由对应的请求。控制器应包含以下方法:

  • index:获取资源列表

  • show:获取单个资源详情

  • store:创建新资源

  • update:更新资源

  • destroy:删除资源

示例:创建 UsersController

src/Controllers/ 目录下创建 UsersController.php 文件:

<?php
// src/Controllers/UsersController.php

namespace MyFrameworkControllers;

use MyFrameworkController;
use MonologLogger;

class UsersController extends Controller
{
    private $logger;

    public function __construct(TwigEnvironment $twig, Logger $logger, MyFrameworkRouter $router)
    {
        parent::__construct($twig, $logger, $router);
        $this->logger = $logger;
    }

    /**
     * 获取用户列表
     */
    public function index()
    {
        $this->logger->info("获取用户列表");

        // 示例数据,实际应用中应从数据库获取
        $users = [
            ['id' => 1, 'name' => '张三', 'email' => 'zhangsan@example.com'],
            ['id' => 2, 'name' => '李四', 'email' => 'lisi@example.com'],
            ['id' => 3, 'name' => '王五', 'email' => 'wangwu@example.com'],
        ];

        $this->jsonResponse($users);
    }

    /**
     * 获取单个用户详情
     *
     * @param int $id 用户ID
     */
    public function show($id)
    {
        $this->logger->info("获取用户详情", ['id' => $id]);

        // 示例数据,实际应用中应从数据库获取
        $users = [
            1 => ['id' => 1, 'name' => '张三', 'email' => 'zhangsan@example.com'],
            2 => ['id' => 2, 'name' => '李四', 'email' => 'lisi@example.com'],
            3 => ['id' => 3, 'name' => '王五', 'email' => 'wangwu@example.com'],
        ];

        if (isset($users[$id])) {
            $this->jsonResponse($users[$id]);
        } else {
            $this->jsonResponse(['error' => '用户未找到'], 404);
        }
    }

    /**
     * 创建新用户
     */
    public function store()
    {
        $this->logger->info("创建新用户");

        // 获取 JSON 请求体
        $input = json_decode(file_get_contents('php://input'), true);

        if (!$input || !isset($input['name']) || !isset($input['email'])) {
            $this->jsonResponse(['error' => '无效的输入'], 400);
            return;
        }

        // 示例:创建用户逻辑,实际应用中应插入数据库
        $newUser = [
            'id' => rand(100, 999), // 示例ID
            'name' => htmlspecialchars($input['name']),
            'email' => htmlspecialchars($input['email']),
        ];

        $this->jsonResponse($newUser, 201);
    }

    /**
     * 更新用户信息
     *
     * @param int $id 用户ID
     */
    public function update($id)
    {
        $this->logger->info("更新用户信息", ['id' => $id]);

        // 获取 JSON 请求体
        $input = json_decode(file_get_contents('php://input'), true);

        if (!$input) {
            $this->jsonResponse(['error' => '无效的输入'], 400);
            return;
        }

        // 示例数据,实际应用中应从数据库获取并更新
        $users = [
            1 => ['id' => 1, 'name' => '张三', 'email' => 'zhangsan@example.com'],
            2 => ['id' => 2, 'name' => '李四', 'email' => 'lisi@example.com'],
            3 => ['id' => 3, 'name' => '王五', 'email' => 'wangwu@example.com'],
        ];

        if (isset($users[$id])) {
            $users[$id] = array_merge($users[$id], array_map('htmlspecialchars', $input));
            $this->jsonResponse($users[$id]);
        } else {
            $this->jsonResponse(['error' => '用户未找到'], 404);
        }
    }

    /**
     * 删除用户
     *
     * @param int $id 用户ID
     */
    public function destroy($id)
    {
        $this->logger->info("删除用户", ['id' => $id]);

        // 示例数据,实际应用中应从数据库删除
        $users = [
            1 => ['id' => 1, 'name' => '张三', 'email' => 'zhangsan@example.com'],
            2 => ['id' => 2, 'name' => '李四', 'email' => 'lisi@example.com'],
            3 => ['id' => 3, 'name' => '王五', 'email' => 'wangwu@example.com'],
        ];

        if (isset($users[$id])) {
            unset($users[$id]);
            $this->jsonResponse(['message' => '用户已删除']);
        } else {
            $this->jsonResponse(['error' => '用户未找到'], 404);
        }
    }

    /**
     * 发送 JSON 响应
     *
     * @param mixed $data 数据
     * @param int $status HTTP 状态码
     */
    protected function jsonResponse($data, $status = 200)
    {
        header_remove();
        http_response_code($status);
        header("Content-Type: application/json");
        echo json_encode($data);
        exit();
    }
}
?>

说明

  • 方法定义

    • index:返回用户列表。

    • show:返回指定 ID 的用户详情。

    • store:创建新用户,接收 JSON 格式的请求体。

    • update:更新指定 ID 的用户信息,接收 JSON 格式的请求体。

    • destroy:删除指定 ID 的用户。

  • JSON 处理

    • 使用 json_decode 解析请求体中的 JSON 数据。

    • 使用 json_encode 发送 JSON 响应,并设置正确的 Content-Type 头。

  • 状态码

    • 正常操作返回 200

    • 创建操作返回 201

    • 输入无效返回 400

    • 资源未找到返回 404


更新 Router 以支持 RESTful 请求

确保路由器能够正确处理不同的 HTTP 方法,并将请求路由到相应的控制器方法。

关键修改点

  1. 支持多种 HTTP 方法

    • 确保路由器能够根据请求的 HTTP 方法(GET、POST、PUT、DELETE)进行路由匹配。

  2. 集成 RESTful 路由

    • 根据定义的 RESTful 路由,将请求转发到相应的控制器方法。

示例:更新 Router 类

以下是关键部分的示例:

<?php
// src/Router.php

namespace MyFramework;

use MyFrameworkMiddlewareMiddleware;
use ReflectionException;

class Router
{
    private $routes = [];
    private $namedRoutes = [];
    private $container;

    /**
     * 构造函数
     *
     * @param Container $container 依赖注入容器
     */
    public function __construct(Container $container)
    {
        $this->container = $container;
        $this->loadCache(); // 如果实现了路由缓存
    }

    /**
     * 添加路由规则
     *
     * @param string $method HTTP 方法
     * @param string $uri 请求的 URI
     * @param string $action 控制器和方法,例如 'UsersController@show'
     * @param array $middlewares 中间件列表
     * @param string|null $name 路由名称
     */
    public function add(string $method, string $uri, string $action, array $middlewares = [], string $name = null)
    {
        // 转换 URI 模式为正则表达式,并提取参数名称
        $pattern = preg_replace_callback('/{([a-zA-Z0-9_]+)(?)?}/', function ($matches) {
            $param = $matches[1];
            $optional = isset($matches[2]) && $matches[2] === '?';
            if ($optional) {
                return '(?P<' . $param . '>[a-zA-Z0-9_-]+)?';
            } else {
                return '(?P<' . $param . '>[a-zA-Z0-9_-]+)';
            }
        }, $uri);

        // 支持可选参数后的斜杠
        $pattern = preg_replace('#//+#', '/', $pattern);
        $pattern = '#^' . $pattern . '(/)?$#';

        // 提取参数名称
        $params = $this->extractParams($uri);

        $this->routes[] = [
            'method'      => strtoupper($method),
            'pattern'     => $pattern,
            'action'      => $action,
            'params'      => $params,
            'middlewares' => $middlewares,
            'name'        => $name
        ];

        if ($name) {
            $this->namedRoutes[$name] = end($this->routes);
        }
    }

    /**
     * 分发请求到相应的控制器方法
     *
     * @param string $requestMethod HTTP 方法
     * @param string $requestUri 请求的 URI
     */
    public function dispatch(string $requestMethod, string $requestUri)
    {
        foreach ($this->routes as $route) {
            if ($route['method'] === strtoupper($requestMethod)) {
                if (preg_match($route['pattern'], $requestUri, $matches)) {
                    // 提取命名参数
                    $params = [];
                    foreach ($route['params'] as $param) {
                        if (isset($matches[$param]) && $matches[$param] !== '') {
                            $params[$param] = $matches[$param];
                        }
                    }

                    // 执行中间件
                    foreach ($route['middlewares'] as $middlewareClass) {
                        /** @var Middleware $middleware */
                        $middleware = $this->container->make($middlewareClass);
                        if (!$middleware->handle($params)) {
                            // 中间件中止请求
                            return;
                        }
                    }

                    // 解析控制器和方法
                    list($controllerName, $method) = explode('@', $route['action']);
                    $fullControllerName = "MyFramework\Controllers\$controllerName";

                    // 通过容器获取控制器实例
                    if ($this->container->make($fullControllerName)) {
                        $controller = $this->container->make($fullControllerName);
                        if (method_exists($controller, $method)) {
                            // 调用方法并传递参数
                            call_user_func_array([$controller, $method], $params);
                            return;
                        }
                    }

                    // 如果控制器或方法不存在,返回 404
                    $this->sendNotFound();
                }
            }
        }
        // 如果没有匹配的路由,返回 404
        $this->sendNotFound();
    }

    /**
     * 发送 404 响应
     */
    private function sendNotFound()
    {
        header("HTTP/1.0 404 Not Found");
        echo json_encode(['error' => '资源未找到']);
        exit();
    }

    /**
     * 提取路由中的参数名称
     *
     * @param string $uri 路由 URI
     * @return array 参数名称列表
     */
    private function extractParams(string $uri): array
    {
        preg_match_all('/{([a-zA-Z0-9_]+)(?)?}/', $uri, $matches);
        return $matches[1];
    }

    // ... 路由缓存相关方法 ...
}
?>

说明

  • HTTP 方法支持:通过路由定义中的 method 参数,路由器能够区分不同的 HTTP 方法(GET、POST、PUT、DELETE)。

  • JSON 404 响应:在 sendNotFound 方法中,返回 JSON 格式的错误信息,符合 RESTful API 的最佳实践。

  • 中间件执行:在分发请求前执行中间件,确保请求的合法性和安全性。


处理 JSON 请求和响应

RESTful API 通常使用 JSON 作为数据交换格式。因此,框架需要能够:

  1. 解析 JSON 请求体:将客户端发送的 JSON 数据解析为 PHP 数组或对象。

  2. 生成 JSON 响应:将 PHP 数据结构编码为 JSON,并设置正确的 Content-Type 头。

示例:在控制器中处理 JSON

UsersController 为例,已在前面的代码中展示了如何处理 JSON 请求和响应。以下是关键部分的总结:

<?php
/**
 * 创建新用户
 */
public function store()
{
    $this->logger->info("创建新用户");

    // 获取 JSON 请求体
    $input = json_decode(file_get_contents('php://input'), true);

    if (!$input || !isset($input['name']) || !isset($input['email'])) {
        $this->jsonResponse(['error' => '无效的输入'], 400);
        return;
    }

    // 创建用户逻辑...

    $this->jsonResponse($newUser, 201);
}

/**
 * 发送 JSON 响应
 *
 * @param mixed $data 数据
 * @param int $status HTTP 状态码
 */
protected function jsonResponse($data, $status = 200)
{
    header_remove();
    http_response_code($status);
    header("Content-Type: application/json");
    echo json_encode($data);
    exit();
}

说明

  • 解析请求:使用 file_get_contents('php://input') 获取原始请求体,并通过 json_decode 解析为 PHP 数组。

  • 生成响应:通过 json_encode 将 PHP 数据结构转换为 JSON,并设置 Content-Typeapplication/json

  • 状态码:根据操作结果设置合适的 HTTP 状态码,如 200201400404 等。


状态码和错误处理

正确使用 HTTP 状态码有助于客户端理解请求的结果。以下是一些常用的状态码及其用途:

  • 200 OK:请求成功,适用于 GET、PUT 等请求。

  • 201 Created:成功创建资源,适用于 POST 请求。

  • 400 Bad Request:客户端请求有误,适用于输入验证失败。

  • 401 Unauthorized:未认证,适用于需要认证的请求。

  • 403 Forbidden:禁止访问,适用于权限不足。

  • 404 Not Found:资源未找到,适用于请求的资源不存在。

  • 500 Internal Server Error:服务器内部错误,适用于未捕获的异常。

示例:在控制器中处理错误

<?php
public function show($id)
{
    $this->logger->info("获取用户详情", ['id' => $id]);

    // 示例数据,实际应用中应从数据库获取
    $users = [
        1 => ['id' => 1, 'name' => '张三', 'email' => 'zhangsan@example.com'],
        2 => ['id' => 2, 'name' => '李四', 'email' => 'lisi@example.com'],
        3 => ['id' => 3, 'name' => '王五', 'email' => 'wangwu@example.com'],
    ];

    if (isset($users[$id])) {
        $this->jsonResponse($users[$id]);
    } else {
        $this->jsonResponse(['error' => '用户未找到'], 404);
    }
}

说明

  • 成功响应:用户存在时,返回 200 OK 和用户数据。

  • 错误响应:用户不存在时,返回 404 Not Found 和错误信息。


示例:实现用户的 RESTful API

以下是一个综合示例,展示如何实现用户资源的 RESTful API,包括路由定义、控制器实现以及 JSON 处理。

1. 路由定义

index.php 中定义用户的 RESTful 路由:

<?php
// index.php

use MyFrameworkRouter;
use MyFrameworkControllersUsersController;
use MyFrameworkMiddlewareAuthenticationMiddleware;

// ... 之前的代码 ...

// 定义 RESTful 用户路由
$router->add('GET', '/users', 'UsersController@index', [AuthenticationMiddleware::class], 'users.index');
$router->add('GET', '/users/{id}', 'UsersController@show', [AuthenticationMiddleware::class], 'users.show');
$router->add('POST', '/users', 'UsersController@store', [AuthenticationMiddleware::class], 'users.store');
$router->add('PUT', '/users/{id}', 'UsersController@update', [AuthenticationMiddleware::class], 'users.update');
$router->add('DELETE', '/users/{id}', 'UsersController@destroy', [AuthenticationMiddleware::class], 'users.destroy');

// 处理请求
$router->dispatch($requestMethod, $requestUri);
?>

2. 控制器实现

UsersController 已在前述部分详细介绍。关键点包括:

  • 使用 jsonResponse 方法返回 JSON 格式的数据。

  • 处理不同的 HTTP 方法对应的 CRUD 操作。

  • 适当使用 HTTP 状态码。

3. 测试 RESTful API

可以使用工具如 PostmancURL 来测试 RESTful API。

a. 获取用户列表

curl -X GET http://localhost:8000/users -H "Authorization: Bearer YOUR_TOKEN"

响应

[
    {"id":1,"name":"张三","email":"zhangsan@example.com"},
    {"id":2,"name":"李四","email":"lisi@example.com"},
    {"id":3,"name":"王五","email":"wangwu@example.com"}
]

b. 获取单个用户

curl -X GET http://localhost:8000/users/1 -H "Authorization: Bearer YOUR_TOKEN"

响应

{"id":1,"name":"张三","email":"zhangsan@example.com"}

c. 创建新用户

curl -X POST http://localhost:8000/users 
     -H "Content-Type: application/json" 
     -H "Authorization: Bearer YOUR_TOKEN" 
     -d '{"name":"赵六","email":"zhaoliu@example.com"}'

响应

{"id":123,"name":"赵六","email":"zhaoliu@example.com"}

d. 更新用户

curl -X PUT http://localhost:8000/users/1 
     -H "Content-Type: application/json" 
     -H "Authorization: Bearer YOUR_TOKEN" 
     -d '{"email":"newemail@example.com"}'

响应

{"id":1,"name":"张三","email":"newemail@example.com"}

e. 删除用户

curl -X DELETE http://localhost:8000/users/1 -H "Authorization: Bearer YOUR_TOKEN"

响应

{"message":"用户已删除"}

说明

  • 认证:假设 AuthenticationMiddleware 已实现,使用合适的认证方式(如 Bearer Token)进行请求认证。

  • 数据格式:所有请求和响应均使用 JSON 格式,符合 RESTful API 的最佳实践。


总结

通过以上步骤,您已经在自定义的 PHP 框架中成功集成了 RESTful 支持,实现了以下功能:

  1. RESTful 路由定义

    • 根据资源的 CRUD 操作,使用合适的 HTTP 方法和 URI 设计路由。

  2. RESTful 控制器实现

    • 创建控制器方法(index、show、store、update、destroy)来处理不同的请求。

  3. JSON 处理

    • 解析 JSON 请求体,生成 JSON 响应,确保数据交换的标准化。

  4. 状态码和错误处理

    • 根据请求结果返回合适的 HTTP 状态码和错误信息,提高 API 的可用性和可理解性。

  5. 中间件保护

    • 使用认证中间件保护敏感的 RESTful 路由,确保只有授权用户可以访问。

优势

  • 一致性:遵循 RESTful 原则,使 API 更加一致和易于理解。

  • 可维护性:通过标准化的路由和控制器方法,提升代码的可维护性和可扩展性。

  • 安全性:结合中间件,实现请求的认证和授权,保障 API 的安全性。

  • 灵活性:使用 JSON 作为数据格式,便于与各种客户端进行数据交换。


进一步扩展建议

为了进一步提升 RESTful API 的功能和用户体验,还可以考虑以下扩展:

  1. 版本控制

    • 在 URI 中引入版本号,如 /api/v1/users,以支持 API 的迭代和向后兼容。

  2. 分页

    • 对于返回大量资源的 index 方法,实现分页功能,优化性能和响应时间。

  3. 过滤和排序

    • 允许客户端通过查询参数进行资源的过滤和排序,提高 API 的灵活性。

  4. 认证和授权

    • 实现更复杂的认证机制,如 OAuth2,确保 API 的安全性。

    • 基于角色的访问控制,限制不同用户的访问权限。

  5. API 文档

    • 使用工具如 SwaggerApiDoc 自动生成 API 文档,提升开发效率和 API 的可用性。

  6. 错误标准化

    • 定义统一的错误响应格式,提供详细的错误信息和解决建议。

  7. 中间件扩展

    • 添加更多的中间件,如日志记录、请求限流、CORS 处理等,提升 API 的功能和性能。

  8. 测试

    • 编写单元测试和集成测试,确保 API 的稳定性和可靠性。

  9. 缓存

    • 对于高频访问的资源,实现响应缓存,提升性能和用户体验。