1. 从零开始理解RESTful API
作为一名有十年PHP开发经验的老兵,我至今记得第一次接触API开发时的困惑。那是在2013年,公司需要为移动端提供数据接口,当时最流行的方案还是SOAP协议。直到后来接触到RESTful架构风格,才发现原来API可以设计得如此简洁优雅。
RESTful API本质上是一套基于HTTP协议的接口设计规范。与传统的RPC式接口不同,它充分利用HTTP协议本身的特性来表达操作语义。举个生活中的例子:传统的API就像打电话给客服,你需要明确告诉客服人员"我要查询订单1234";而RESTful API则像是使用自助服务机 - 你只需要访问/orders/1234这个地址,系统就会自动返回对应的订单信息。
在PHP中实现RESTful API,核心是要理解以下几个关键概念:
- 资源(Resource):将业务数据抽象为资源,比如用户、商品、订单等
- 统一接口:使用标准的HTTP方法(GET/POST/PUT/DELETE等)来操作资源
- 无状态:每个请求应包含处理所需的所有信息,服务端不保存客户端状态
- 表述性:资源可以有多种表现形式(JSON/XML等),客户端通过Accept头指定
新手常见误区:很多初学者会把RESTful API简单理解为"返回JSON数据的接口",这其实只看到了表象。真正的RESTful设计应该遵循上述原则。
2. 基础环境准备与项目结构
2.1 开发环境配置
我推荐使用PHP 7.4+版本进行API开发,新版本在性能和安全性上都有显著提升。以下是推荐的基础环境:
- Web服务器:Apache/Nginx
- PHP扩展:json(必须)、pdo(数据库操作)、openssl(安全通信)
- 开发工具:Postman(API测试)、Composer(依赖管理)
对于初学者,可以使用XAMPP或Docker快速搭建环境。我个人更倾向Docker,因为它能保持开发环境的一致性。以下是一个简单的docker-compose.yml配置:
version: '3' services: web: image: php:7.4-apache ports: - "8080:80" volumes: - ./src:/var/www/html depends_on: - db db: image: mysql:5.7 environment: MYSQL_ROOT_PASSWORD: example MYSQL_DATABASE: api_demo2.2 项目目录结构
良好的目录结构能让项目更易维护。这是我常用的API项目结构:
/api-demo/ ├── app/ │ ├── controllers/ # 控制器 │ ├── models/ # 数据模型 │ └── core/ # 核心类库 ├── config/ │ └── database.php # 数据库配置 ├── public/ │ └── index.php # 入口文件 ├── routes/ │ └── api.php # 路由定义 └── vendor/ # Composer依赖这种结构遵循了单一职责原则,将不同功能的代码分离,便于团队协作和后期扩展。
3. 核心实现步骤详解
3.1 处理HTTP请求
RESTful API的核心是根据不同的HTTP方法执行对应操作。在纯PHP中,我们可以通过$_SERVER['REQUEST_METHOD']获取请求方法:
$method = $_SERVER['REQUEST_METHOD']; switch ($method) { case 'GET': // 获取资源 break; case 'POST': // 创建资源 break; case 'PUT': // 更新资源 break; case 'DELETE': // 删除资源 break; default: http_response_code(405); // Method Not Allowed echo json_encode(['error' => 'Method not supported']); }对于更复杂的场景,我建议使用轻量级路由库如AltoRouter。以下是使用示例:
require_once 'vendor/autoload.php'; $router = new AltoRouter(); // 定义路由 $router->map('GET', '/users', 'UserController#getAll'); $router->map('GET', '/users/[i:id]', 'UserController#getById'); $router->map('POST', '/users', 'UserController#create'); // 匹配当前请求 $match = $router->match(); if ($match) { list($controller, $action) = explode('#', $match['target']); call_user_func_array([new $controller, $action], $match['params']); } else { header($_SERVER["SERVER_PROTOCOL"] . ' 404 Not Found'); }3.2 数据格式处理
现代API主要使用JSON作为数据交换格式。PHP提供了便捷的json_encode和json_decode函数:
// 输出JSON响应 header('Content-Type: application/json'); echo json_encode([ 'success' => true, 'data' => [ 'id' => 123, 'name' => '示例用户' ] ]); // 接收JSON请求 $input = json_decode(file_get_contents('php://input'), true); if (json_last_error() !== JSON_ERROR_NONE) { http_response_code(400); // Bad Request echo json_encode(['error' => 'Invalid JSON']); exit; }重要提示:务必设置正确的Content-Type头,并处理JSON解析错误。我曾遇到过因为缺少错误处理导致系统崩溃的案例。
3.3 数据库操作示例
以下是使用PDO进行数据库操作的典型模式:
class UserModel { private $db; public function __construct() { $this->db = new PDO('mysql:host=db;dbname=api_demo', 'root', 'example'); $this->db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); } public function getById($id) { $stmt = $this->db->prepare("SELECT * FROM users WHERE id = ?"); $stmt->execute([$id]); return $stmt->fetch(PDO::FETCH_ASSOC); } public function create($data) { $stmt = $this->db->prepare("INSERT INTO users (name, email) VALUES (?, ?)"); $stmt->execute([$data['name'], $data['email']]); return $this->db->lastInsertId(); } }4. 完整API示例:用户管理系统
4.1 用户控制器实现
class UserController { private $model; public function __construct() { $this->model = new UserModel(); } public function getAll() { try { $users = $this->model->getAll(); http_response_code(200); echo json_encode($users); } catch (Exception $e) { http_response_code(500); echo json_encode(['error' => $e->getMessage()]); } } public function getById($id) { try { $user = $this->model->getById($id); if ($user) { http_response_code(200); echo json_encode($user); } else { http_response_code(404); echo json_encode(['error' => 'User not found']); } } catch (Exception $e) { http_response_code(500); echo json_encode(['error' => $e->getMessage()]); } } public function create() { $input = json_decode(file_get_contents('php://input'), true); // 验证输入 if (empty($input['name']) || empty($input['email'])) { http_response_code(400); echo json_encode(['error' => 'Missing required fields']); return; } try { $id = $this->model->create($input); http_response_code(201); echo json_encode(['id' => $id]); } catch (Exception $e) { http_response_code(500); echo json_encode(['error' => $e->getMessage()]); } } }4.2 路由配置
$router->map('GET', '/users', 'UserController#getAll'); $router->map('GET', '/users/[i:id]', 'UserController#getById'); $router->map('POST', '/users', 'UserController#create'); $router->map('PUT', '/users/[i:id]', 'UserController#update'); $router->map('DELETE', '/users/[i:id]', 'UserController#delete');5. 进阶技巧与最佳实践
5.1 认证与授权
API安全是重中之重。以下是几种常见的认证方案:
- Basic Auth:最简单的方式,但不安全,仅建议用于内部API
if (!isset($_SERVER['PHP_AUTH_USER'])) { header('WWW-Authenticate: Basic realm="My API"'); http_response_code(401); exit; } if ($_SERVER['PHP_AUTH_USER'] != 'admin' || $_SERVER['PHP_AUTH_PW'] != 'password') { http_response_code(403); echo json_encode(['error' => 'Invalid credentials']); exit; }- JWT(推荐):使用composer安装firebase/php-jwt
use Firebase\JWT\JWT; // 生成Token $key = 'your-secret-key'; $payload = [ 'user_id' => 123, 'exp' => time() + 3600 // 1小时过期 ]; $jwt = JWT::encode($payload, $key); // 验证Token try { $decoded = JWT::decode($jwt, $key, ['HS256']); } catch (Exception $e) { http_response_code(401); echo json_encode(['error' => 'Invalid token']); exit; }5.2 输入验证与过滤
永远不要信任客户端输入。以下是一些验证技巧:
// 验证邮箱 if (!filter_var($input['email'], FILTER_VALIDATE_EMAIL)) { http_response_code(400); echo json_encode(['error' => 'Invalid email format']); exit; } // 过滤HTML标签 $name = filter_var($input['name'], FILTER_SANITIZE_STRING); // 验证数字范围 if ($input['age'] < 18 || $input['age'] > 120) { http_response_code(400); echo json_encode(['error' => 'Age must be between 18 and 120']); exit; }5.3 错误处理与日志记录
良好的错误处理能极大提升API的健壮性:
// 自定义错误处理器 set_error_handler(function($errno, $errstr, $errfile, $errline) { error_log("[$errno] $errstr in $errfile on line $errline"); http_response_code(500); echo json_encode(['error' => 'An internal error occurred']); exit; }); // 异常处理 set_exception_handler(function($e) { error_log("Exception: " . $e->getMessage()); http_response_code(500); echo json_encode(['error' => 'An unexpected error occurred']); exit; }); // 记录请求日志 file_put_contents('api.log', date('Y-m-d H:i:s') . ' ' . $_SERVER['REQUEST_METHOD'] . ' ' . $_SERVER['REQUEST_URI'] . "\n", FILE_APPEND);6. 性能优化技巧
6.1 使用OPcache
PHP 5.5+内置了OPcache扩展,能显著提升性能:
; php.ini配置 opcache.enable=1 opcache.memory_consumption=128 opcache.max_accelerated_files=4000 opcache.revalidate_freq=606.2 数据库优化
- 使用预处理语句防止SQL注入
- 合理使用索引
- 考虑添加查询缓存
// 使用预处理语句 $stmt = $db->prepare("SELECT * FROM users WHERE id = ?"); $stmt->execute([$id]); // 添加索引 CREATE INDEX idx_user_email ON users(email);6.3 响应缓存
对于不常变动的数据,可以考虑添加HTTP缓存头:
header('Cache-Control: public, max-age=3600'); // 缓存1小时 header('Expires: ' . gmdate('D, d M Y H:i:s', time() + 3600) . ' GMT');7. 常见问题与解决方案
7.1 跨域问题(CORS)
当API和前端不同源时,需要处理CORS:
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') { http_response_code(200); exit; }7.2 文件上传处理
if ($_FILES['avatar']['error'] === UPLOAD_ERR_OK) { $tmpName = $_FILES['avatar']['tmp_name']; $fileName = uniqid() . '.' . pathinfo($_FILES['avatar']['name'], PATHINFO_EXTENSION); $destination = '/uploads/' . $fileName; if (move_uploaded_file($tmpName, $destination)) { // 保存文件路径到数据库 } else { http_response_code(500); echo json_encode(['error' => 'File upload failed']); } }7.3 分页实现
public function getList($page = 1, $perPage = 10) { $offset = ($page - 1) * $perPage; $stmt = $this->db->prepare("SELECT * FROM users LIMIT ? OFFSET ?"); $stmt->execute([$perPage, $offset]); // 获取总数 $total = $this->db->query("SELECT COUNT(*) FROM users")->fetchColumn(); return [ 'data' => $stmt->fetchAll(PDO::FETCH_ASSOC), 'pagination' => [ 'total' => $total, 'per_page' => $perPage, 'current_page' => $page, 'last_page' => ceil($total / $perPage) ] ]; }在实际项目中,我发现很多团队会忽略API文档的重要性。使用Swagger或OpenAPI规范可以自动生成API文档,大大提升前后端协作效率。虽然本文重点介绍了基础实现,但在实际企业级开发中,建议考虑使用Laravel、Symfony等成熟框架,它们提供了更完善的API开发工具链。