1. 前言
做 PHP 开发这些年,踩过不少坑,也积累了一些实战经验。今天想跟大家聊聊怎么从零开始搭建一个真正能上线的 RESTful API 服务。不是那种框架一键生成的 Hello World,而是从路由设计、错误处理、数据库交互到性能优化,一步步把每个环节都讲清楚。
很多新手写 PHP 接口,习惯把业务逻辑全塞在一个文件里,数据库查询直接写在控制器中,错误处理全靠 try-catch 兜底。这种写法在小项目里跑得通,一旦业务复杂起来,维护成本直线上升。今天这篇文章,我会用最朴素的 PHP 原生写法,配合 Composer 管理依赖,带你写出一套结构清晰、易于扩展的 API 框架。
2. 项目初始化与环境准备
工欲善其事,必先利其器。在开始写代码之前,先把开发环境搭好。我推荐使用 PHP 8.1 以上版本,因为新版本带来了枚举、只读属性、构造器属性提升等非常实用的语法糖。
2.1 目录结构设计
一个好的目录结构,能让项目后期维护省心不少。我习惯这样组织:
project/
├── public/ # 入口文件
│ └── index.php
├── src/ # 核心代码
│ ├── Controllers/
│ ├── Models/
│ ├── Middleware/
│ ├── Router.php
│ └── Database.php
├── config/ # 配置文件
│ └── app.php
├── vendor/ # Composer 依赖
└── composer.json
这种分层方式借鉴了 MVC 的思想,但又不至于太重。每个目录各司其职,Controller 只负责接收请求和返回响应,Model 处理数据逻辑,Middleware 做请求预处理。
2.2 引入 Composer 自动加载
在项目根目录创建 composer.json:
json
{
"autoload": {
"psr-4": {
"App\\": "src/"
}
}
}
然后执行 composer dump-autoload 生成自动加载文件。这样我们就能用命名空间来组织类文件,告别 require_once 满天飞的日子。
3. 路由系统的设计与实现
路由是 API 框架的骨架。我见过很多项目用 if-else 判断请求 URI,代码又长又难维护。不如自己写一个轻量路由类。
3.1 路由注册与匹配
php
<?php
namespace App;
class Router
{
private array $routes = [];
public function get(string $path, callable $handler): void
{
$this->routes['GET'][$path] = $handler;
}
public function post(string $path, callable $handler): void
{
$this->routes['POST'][$path] = $handler;
}
public function dispatch(string $method, string $uri): void
{
$uri = parse_url($uri, PHP_URL_PATH);
if (!isset($this->routes[$method][$uri])) {
http_response_code(404);
echo json_encode(['error' => 'Not Found']);
return;
}
call_user_func($this->routes[$method][$uri]);
}
}
这个路由类虽然简单,但已经能满足大部分场景。如果后续需要支持参数路由(比如 /users/{id}),可以在此基础上扩展正则匹配逻辑。
3.2 入口文件整合
在 public/index.php 中整合路由:
php
<?php
require_once __DIR__ . '/../vendor/autoload.php';
use App\Router;
use App\Controllers\UserController;
$router = new Router();
$router->get('/api/users', [UserController::class, 'index']);
$router->post('/api/users', [UserController::class, 'store']);
$router->get('/api/users/1', [UserController::class, 'show']);
$router->dispatch($_SERVER['REQUEST_METHOD'], $_SERVER['REQUEST_URI']);
这里有个细节:/api/users/1 这种带 ID 的路由,后续可以改成动态参数匹配。先写死是为了让逻辑更清晰,方便理解路由的工作原理。
4. 数据库交互与模型层设计
PHP 连接数据库的方式很多,PDO 是我最推荐的一种。它提供了统一的接口,支持预处理语句,能有效防止 SQL 注入。
4.1 数据库连接封装
php
<?php
namespace App;
use PDO;
use PDOException;
class Database
{
private static ?PDO $instance = null;
public static function getConnection(): PDO
{
if (self::$instance === null) {
$config = require __DIR__ . '/../config/app.php';
try {
self::$instance = new PDO(
"mysql:host={$config['db_host']};dbname={$config['db_name']};charset=utf8mb4",
$config['db_user'],
$config['db_pass'],
[
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
]
);
} catch (PDOException $e) {
http_response_code(500);
echo json_encode(['error' => 'Database connection failed']);
exit;
}
}
return self::$instance;
}
}
这里用了单例模式,确保整个请求周期内只创建一个数据库连接。ATTR_EMULATE_PREPARES 设为 false 是让 PDO 使用原生的预处理,而不是模拟,对性能有好处。
4.2 模型层实现
php
<?php
namespace App\Models;
use App\Database;
class User
{
public static function all(): array
{
$stmt = Database::getConnection()->query('SELECT id, name, email FROM users');
return $stmt->fetchAll();
}
public static function find(int $id): ?array
{
$stmt = Database::getConnection()->prepare('SELECT id, name, email FROM users WHERE id = :id');
$stmt->execute(['id' => $id]);
$result = $stmt->fetch();
return $result ?: null;
}
public static function create(array $data): array
{
$stmt = Database::getConnection()->prepare(
'INSERT INTO users (name, email) VALUES (:name, :email)'
);
$stmt->execute([
'name' => $data['name'],
'email' => $data['email'],
]);
$id = Database::getConnection()->lastInsertId();
return self::find((int)$id);
}
}
模型层只做数据查询和组装,不掺入任何 HTTP 相关的逻辑。这样如果以后要切换到其他数据源(比如 Redis 缓存),只需要改模型层,控制器完全不用动。
5. 控制器与请求处理
控制器是连接路由和模型的桥梁。一个好的控制器应该足够薄,只做三件事:接收输入、调用模型、返回响应。
php
<?php
namespace App\Controllers;
use App\Models\User;
class UserController
{
public function index(): void
{
$users = User::all();
http_response_code(200);
echo json_encode([
'success' => true,
'data' => $users,
]);
}
public function show(int $id): void
{
$user = User::find($id);
if (!$user) {
http_response_code(404);
echo json_encode(['error' => 'User not found']);
return;
}
http_response_code(200);
echo json_encode([
'success' => true,
'data' => $user,
]);
}
public function store(): void
{
$input = json_decode(file_get_contents('php://input'), true);
if (empty($input['name']) || empty($input['email'])) {
http_response_code(422);
echo json_encode(['error' => 'Name and email are required']);
return;
}
$user = User::create([
'name' => $input['name'],
'email' => $input['email'],
]);
http_response_code(201);
echo json_encode([
'success' => true,
'data' => $user,
]);
}
}
注意看 store 方法里的输入验证。很多新手会忽略这一步,直接把用户输入塞进数据库。这里做了简单的非空校验,实际项目中还可以用专门的验证器类来做更复杂的规则校验。
6. 中间件与错误处理
6.1 实现一个简单的中间件机制
中间件可以在请求到达控制器之前做一些预处理,比如鉴权、日志记录、CORS 头设置等。
php
<?php
namespace App\Middleware;
class CorsMiddleware
{
public static function handle(): void
{
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(204);
exit;
}
}
}
然后在入口文件中调用:
php
use App\Middleware\CorsMiddleware;
CorsMiddleware::handle();
6.2 全局异常处理
PHP 默认的错误提示在 API 场景下很不友好。我们可以注册一个全局异常处理器,把异常统一转换成 JSON 格式返回。
php
set_exception_handler(function (Throwable $e) {
http_response_code(500);
echo json_encode([
'error' => 'Internal Server Error',
'message' => $e->getMessage(),
]);
});
这个处理器的好处是,不管代码哪里抛了异常,最终都会以统一的 JSON 格式返回给客户端,不会出现 PHP 默认的堆栈信息泄露。
7. 性能优化实战
7.1 使用 OPcache
PHP 8 内置的 OPcache 能显著提升性能。在 php.ini 中开启:
ini
opcache.enable=1
opcache.memory_consumption=128
opcache.max_accelerated_files=10000
opcache.revalidate_freq=2
生产环境下,revalidate_freq 可以设大一些,减少文件检查的开销。
7.2 数据库查询优化
- 尽量使用索引,尤其是 WHERE 条件和 JOIN 关联的字段
- 避免在循环中执行 SQL 查询,能用一次查询解决的绝不分多次
- 对于频繁读取但不常变化的数据,用 Redis 做缓存
7.3 响应压缩
在 Nginx 层面开启 Gzip 压缩,能减少 60% 以上的传输体积:
nginx
gzip on;
gzip_types application/json text/plain text/css;
gzip_min_length 1000;
8. 总结
这篇文章从路由设计、数据库交互、控制器实现到性能优化,完整走了一遍 PHP RESTful API 的开发流程。核心思想就一句话:职责分离,各司其职。路由只管分发,控制器只管协调,模型只管数据,中间件只管预处理。
这套架构虽然简单,但我在多个生产项目中验证过,稳定性和可维护性都经得起考验。如果你正在从零搭建 PHP 项目,不妨试试这个思路。当然,实际项目中还有很多细节要考虑,比如接口版本管理、限流、日志记录等,这些留到后续文章再聊。
希望这篇文章对你有帮助。如果你有更好的实践,欢迎在评论区交流。