综合练习
只读 tracks JSON API 迷你脚本。
40 分钟
综合练习
这一课你将能做到
- 从零搭出结构规范的迷你博客后端:PSR-4 自动加载 + 前端控制器 + 分层类
- 把全路线知识拼进一个项目:Composer、PDO 预处理、Session 登录、统一 JSON 响应
- 用 curl 完成 API 验收,明确下一步学框架(Laravel/Symfony)的衔接点
为什么要学
前 11 课的知识都是散装积木。这一课把它们拼成一个能跑的完整项目——项目结构是工程能力的照妖镜:入口是否唯一、SQL 是否安全、职责是否分层。用原生把框架要做的事手动做一遍,之后学 Laravel 就是「认旧朋友」。
先建立图像
mini-blog/
├── public/index.php # 唯一入口(前端控制器)
├── src/
│ ├── Database.php # PDO 连接(每请求单例)
│ ├── Auth.php # Session 登录态 + CSRF
│ ├── PostRepository.php # 数据访问层(只管 SQL)
│ └── PostController.php # 控制器(校验→调仓储→响应)
├── composer.json
└── posts.sql
public 是「门面」:Web 根只指向它,src 里的类文件永远不会被 URL 直接访问——这是安全底线。
跟着做
步骤 1:初始化工程
composer.json 只需两块:"require": {"php": ">=8.2"} 与 "autoload": {"psr-4": {"App\\": "src/"}},改完执行 composer dump-autoload。
表 posts 五列:id 自增主键、title VARCHAR(80)、content TEXT、author VARCHAR(40)、created_at DATETIME,InnoDB 引擎 + utf8mb4 字符集。
步骤 2:Database 与 Auth
src/Database.php:静态属性缓存连接的请求级单例,配置沿用 PDO 课三件套——ERRMODE_EXCEPTION、FETCH_ASSOC、EMULATE_PREPARES=false。
src/Auth.php——登录态与 CSRF:
<?php
declare(strict_types=1);
namespace App;
final class Auth
{
public static function boot(): void
{
session_set_cookie_params(['httponly' => true, 'samesite' => 'Lax']);
session_start();
}
public static function login(string $user): void
{
$_SESSION['user'] = $user;
$_SESSION['csrf'] = bin2hex(random_bytes(16));
}
public static function user(): ?string { return $_SESSION['user'] ?? null; }
public static function csrf(): string { return $_SESSION['csrf'] ?? ''; }
}
步骤 3:数据访问层与控制器
src/PostRepository.php——只写 SQL,不掺路由与响应:
<?php
declare(strict_types=1);
namespace App;
final class PostRepository
{
public function __construct(private \PDO $pdo) {}
public function all(): array
{
return $this->pdo->query(
'SELECT id, title, author, created_at FROM posts ORDER BY id DESC LIMIT 20'
)->fetchAll();
}
public function create(string $title, string $content, string $author): int
{
$stmt = $this->pdo->prepare(
'INSERT INTO posts (title, content, author) VALUES (?, ?, ?)'
);
$stmt->execute([$title, $content, $author]);
return (int) $this->pdo->lastInsertId();
}
public function delete(int $id): bool
{
$stmt = $this->pdo->prepare('DELETE FROM posts WHERE id = ?');
$stmt->execute([$id]);
return $stmt->rowCount() > 0;
}
}