目录¶
理解 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 方法,并将请求路由到相应的控制器方法。
关键修改点¶
支持多种 HTTP 方法:
确保路由器能够根据请求的 HTTP 方法(GET、POST、PUT、DELETE)进行路由匹配。
集成 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 作为数据交换格式。因此,框架需要能够:
解析 JSON 请求体:将客户端发送的 JSON 数据解析为 PHP 数组或对象。
生成 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-Type
为application/json
。状态码:根据操作结果设置合适的 HTTP 状态码,如
200
、201
、400
、404
等。
状态码和错误处理¶
正确使用 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¶
可以使用工具如 Postman 或 cURL 来测试 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 支持,实现了以下功能:
RESTful 路由定义:
根据资源的 CRUD 操作,使用合适的 HTTP 方法和 URI 设计路由。
RESTful 控制器实现:
创建控制器方法(index、show、store、update、destroy)来处理不同的请求。
JSON 处理:
解析 JSON 请求体,生成 JSON 响应,确保数据交换的标准化。
状态码和错误处理:
根据请求结果返回合适的 HTTP 状态码和错误信息,提高 API 的可用性和可理解性。
中间件保护:
使用认证中间件保护敏感的 RESTful 路由,确保只有授权用户可以访问。
优势:
一致性:遵循 RESTful 原则,使 API 更加一致和易于理解。
可维护性:通过标准化的路由和控制器方法,提升代码的可维护性和可扩展性。
安全性:结合中间件,实现请求的认证和授权,保障 API 的安全性。
灵活性:使用 JSON 作为数据格式,便于与各种客户端进行数据交换。
进一步扩展建议¶
为了进一步提升 RESTful API 的功能和用户体验,还可以考虑以下扩展:
版本控制:
在 URI 中引入版本号,如
/api/v1/users
,以支持 API 的迭代和向后兼容。
分页:
对于返回大量资源的
index
方法,实现分页功能,优化性能和响应时间。
过滤和排序:
允许客户端通过查询参数进行资源的过滤和排序,提高 API 的灵活性。
认证和授权:
实现更复杂的认证机制,如 OAuth2,确保 API 的安全性。
基于角色的访问控制,限制不同用户的访问权限。
API 文档:
使用工具如 Swagger 或 ApiDoc 自动生成 API 文档,提升开发效率和 API 的可用性。
错误标准化:
定义统一的错误响应格式,提供详细的错误信息和解决建议。
中间件扩展:
添加更多的中间件,如日志记录、请求限流、CORS 处理等,提升 API 的功能和性能。
测试:
编写单元测试和集成测试,确保 API 的稳定性和可靠性。
缓存:
对于高频访问的资源,实现响应缓存,提升性能和用户体验。