xiaououo / xiaophp
基于 PHP 的轻量级 MVC 框架,适合小型项目快速开发
Requires
None
Requires (Dev)
None
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
作者:小新
版本:V2.1.3
许可证:Apache-2.0
文档生成日期:2026-08-26
代码规模:42 个 PHP 文件,约 4388 行代码
目录
- 第一章 框架概述
- 第二章 目录结构详解
- 第三章 快速开始
- 第四章 架构设计
- 第五章 配置系统
- 第六章 路由系统
- 第七章 控制器
- 第八章 模型与数据库
- 第九章 视图引擎
- 第十章 中间件与认证
- 第十一章 缓存系统
- 第十二章 日志系统
- 第十三章 数据验证器
- 第十四章 文件管理
- 第十五章 HTTP 客户端
- 第十六章 加密工具
- 第十七章 Redis 操作
- 第十八章 阿里云 DNS
- 第十九章 错误处理与调试
- 第二十章 CLI 命令行
- 第二十一章 安全审计
- 第二十二章 性能分析
- 第二十三章 扩展开发指南
- 第二十四章 最佳实践
- 第二十五章 常见问题 FAQ
- 第二十六章 版本历史与升级
- 附录 A 全局函数与常量速查
- 附录 B 配置项完整清单
- 附录 C 类与命名空间索引
第一章 框架概述
1.1 框架定位
XiaoPHP 是一款由个人开发者「小新」维护的轻量级 PHP MVC 框架,专为小型项目快速开发而设计。框架采用零 Composer 依赖的核心架构(Composer 为可选增强),通过自定义自动加载器实现类的自动装载,内置完整的 MVC 分层、路由分发、数据库 ORM、模板引擎、中间件认证、缓存、日志、验证、文件上传、加密、HTTP 客户端等常用能力。
1.2 核心特性
| 特性 | 说明 |
|---|---|
| 轻量零依赖 | 核心不依赖任何 Composer 包,解压即用 |
| MVC 分层 | App/应用 → Controller/Model/View 标准分层 |
| 多应用支持 | App 目录下可创建多个独立应用,每个应用可独立开关 |
| 双路由模式 | 精确路由(自定义)+ 自动路由(约定优于配置) |
| PDO 查询构造器 | 链式调用、预处理语句防注入、事务支持 |
| 自研模板引擎 | 支持继承、区块、包含、循环、条件、自动转义、编译缓存 |
| Token 中间件 | 支持 Bearer/POST/Cookie 多模式,文件/Redis 双存储 |
| Session 认证 | 内置 Auth 类,支持角色权限、CSRF 双 Token |
| DI 容器 | 支持绑定、单例、自动装配、循环依赖检测 |
| CLI 脚手架 | 命令行创建应用、执行脚手架命令 |
| 完整错误页 | 401/403/404 自定义 HTML 错误页 + 调试信息页 |
1.3 技术栈要求
- PHP 版本:>= 7.4(推荐 8.0+,代码使用了
declare(strict_types=1)、命名参数等特性) - 数据库:MySQL 5.7+ / MariaDB(通过 PDO 连接)
- 缓存:可选 Redis(需 phpredis 扩展),默认文件缓存
- Web 服务器:Nginx(推荐)/ Apache
- PHP 扩展:pdo_mysql、openssl、mbstring、curl、fileinfo(文件上传需要)
1.4 设计哲学
- 约定优于配置:自动路由按
/应用名/控制器/方法约定分发,无需手动注册每条路由。 - 零依赖核心:框架核心不捆绑第三方库,降低部署门槛和供应链风险。
- 安全优先:数据库操作全部使用 PDO 预处理;视图默认 HTML 转义;上传文件扫描 PHP 标签;Token 使用 md5 哈希存储。
- 渐进式增强:从最简单的
echo输出到完整的 MVC + ORM + 模板,开发者可按需选用能力。
第二章 目录结构详解
2.1 整体目录树
XiaoPHP/ # 框架根目录
├── App/ # 应用目录(多应用)
│ ├── Index/ # 默认应用「Index」
│ │ ├── Config/ # 应用级配置(可选)
│ │ ├── Controller/ # 控制器
│ │ │ └── Index.php
│ │ ├── Function/ # 应用级函数/类库(自动加载)
│ │ ├── Model/ # 数据模型(自动加载)
│ │ ├── View/ # 视图模板
│ │ │ └── index.html
│ │ └── app.json # 应用配置(开关、版本、描述)
│ └── Loading.php # 应用加载器(扫描并 require 所有应用的 Config/Function/Model)
├── Config/ # 全局配置目录
│ ├── AliyunDns.php # 阿里云 DNS 配置
│ ├── App.php # 应用配置(调试模式、错误格式)
│ ├── Cache.php # 缓存配置
│ ├── Logs.php # 日志配置
│ ├── Middleware.php # 中间件配置
│ ├── Mysql.php # 数据库配置
│ └── Redis.php # Redis 配置
├── Public/ # Web 根目录(唯一对外暴露目录)
│ ├── index.php # 框架入口文件
│ └── nginx.htaccess # Nginx 伪静态配置示例
├── Resources/ # 静态资源目录
│ ├── PHPFile/ # 上传文件存储目录
│ └── 2.txt # 示例静态文件
├── Route/ # 自定义路由注册
│ └── Route.php # 路由定义文件
├── Scaffold/ # CLI 脚手架命令
│ └── Apptools.php # 示例脚手架命令
├── Whitelist/ # 白名单配置
│ └── Whitelist.php # 免认证路由白名单
├── XiaoPHP/ # 框架核心目录
│ ├── console.php # 核心依赖加载器(8 阶段启动)
│ ├── debug.php # 调试信息显示(异常渲染页)
│ ├── start.php # 错误/异常处理注册
│ ├── Middleware.php # 中间件类(Token 认证)
│ ├── Routing.php # 路由分发器
│ ├── Scaffold.php # CLI 脚手架调度器
│ └── System/
│ ├── Error/ # 错误页面
│ │ ├── 401.html
│ │ ├── 403.html
│ │ ├── 404.html
│ │ └── error.php # 全局 Error() 函数
│ └── Tools/
│ ├── App/ # 应用级工具
│ │ ├── AliyunDns.php # 阿里云 DNS API 封装
│ │ ├── MysqlTools.php # PDO 查询构造器 / ORM
│ │ └── RedisTools.php # Redis 封装
│ ├── Config/ # 配置管理
│ │ ├── Conf.php # 配置读取器
│ │ ├── Env.php # .env 环境变量加载器
│ │ ├── Route.php # 路由注册器
│ │ ├── ServiceProvider.php # 服务提供者
│ │ └── Whitelist.php # 白名单管理器
│ ├── encrypt/ # 加密工具
│ │ ├── AesTool.php # AES 加密
│ │ └── RSATool.php # RSA 加密
│ └── Function/ # 核心功能类
│ ├── Auth.php # Session 认证
│ ├── Cache.php # 文件缓存
│ ├── Container.php # DI 容器
│ ├── File.php # 文件上传/管理
│ ├── Helper.php # 辅助工具(大小写不敏感查找)
│ ├── Ipaddr.php # IP 地址获取
│ ├── Json.php # JSON 输出/远程获取
│ ├── Logs.php # 日志记录
│ ├── Model.php # 模型基类
│ ├── Validate.php # 数据验证器
│ ├── View.php # 模板引擎
│ └── Wget.php # HTTP 客户端
├── .env # 环境变量文件
├── composer.json # Composer 配置(可选)
├── composer.lock
└── XiaoCTL # CLI 命令行入口
2.2 关键目录说明
| 目录 | 作用 | 是否对外暴露 |
|---|---|---|
Public/ |
唯一 Web 根目录,存放入口文件 | 是 |
App/ |
业务应用代码,多应用隔离 | 否 |
Config/ |
全局配置文件 | 否 |
XiaoPHP/ |
框架核心,禁止修改 | 否 |
Resources/ |
静态资源与上传文件 | 部分(通过路由访问) |
Route/ |
自定义路由定义 | 否 |
Whitelist/ |
免认证白名单定义 | 否 |
Scaffold/ |
CLI 脚手架命令 | 否 |
2.3 运行时目录(自动创建)
框架在运行时会自动创建以下目录:
| 目录 | 用途 |
|---|---|
Temp/Cache/ |
文件缓存存储 |
Temp/View/ |
视图编译缓存 |
logs/success/ |
成功请求日志 |
logs/error/ |
错误请求日志 |
runtime/sessions/ |
Session 存储(当系统 session 目录不可写时回退) |
第三章 快速开始
3.1 安装
XiaoPHP 无需 Composer 安装,直接解压即可:
# 解压框架 unzip XiaoPHP2.1.3.zip cd XiaoPHP # 设置目录权限(Web 服务器用户需要写入权限) chmod -R 755 Public/ chmod -R 777 Temp/ logs/ runtime/ Resources/PHPFile/ 2>/dev/null || true
3.2 环境配置
编辑根目录下的 .env 文件:
# MySQL 数据库 DB_HOST=127.0.0.1 DB_PORT=3306 DB_NAME=your_database DB_USER=your_username DB_PASSWORD=your_password # Redis(可选) REDIS_HOST=127.0.0.1 REDIS_PORT=6379 REDIS_PASSWORD= # 框架调试模式 DEBUG=false
安全提示:
.env文件包含敏感信息,必须确保 Web 服务器不会直接访问到该文件。Nginx 配置中应禁止访问.env。
3.3 Nginx 配置
server { listen 80; server_name your-domain.com; root /path/to/XiaoPHP/Public; index index.php; # 禁止访问隐藏文件 location ~ /\. { deny all; } # 伪静态(框架已提供 nginx.htaccess) location / { try_files $uri $uri/ /index.php?$args; } # PHP 解析 location ~ \.php$ { fastcgi_pass unix:/run/php/php8.1-fpm.sock; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; } }
3.4 验证安装
浏览器访问 http://your-domain.com/,应看到「欢迎使用 XiaoPHP / 轻量级 MVC 框架 / V2.1.3」的默认欢迎页。
3.5 第一个控制器
在 App/Index/Controller/ 下创建 Hello.php:
<?php class Hello { public function Main() { return "Hello, XiaoPHP!"; } public function world() { return json_encode(['msg' => 'Hello World']); } }
访问:
http://your-domain.com/Index/Hello/Main→ 输出Hello, XiaoPHP!http://your-domain.com/Index/Hello/world→ 输出 JSON
路由规则:
/应用名/控制器名/方法名,方法名不区分大小写。
第四章 架构设计
4.1 请求生命周期
XiaoPHP 的一次 HTTP 请求经历以下完整流程:
用户请求
│
▼
Nginx/Apache 重写 → Public/index.php
│
├─ 1. 开启输出缓冲 ob_start()
├─ 2. 检测 Session 存储目录可写性,不可写则回退到 runtime/sessions
├─ 3. 启动 Session
├─ 4. 定义常量 SYS_PATH, SYS_ROOT
│
▼
XiaoPHP/console.php(核心加载器,8 阶段启动)
│
├─ Phase 1: 注册自定义自动加载器(spl_autoload_register)
├─ Phase 2: 加载 Composer 自动加载(如存在 vendor/autoload.php)
├─ Phase 3: 注册错误处理(error.php → debug.php → start.php)
├─ Phase 4: 加载核心基础设施(Env → Conf → Container → Helper → Ipaddr)
├─ Phase 5: 加载 Config/*.php 全局配置文件
├─ Phase 6: ServiceProvider::register() 注册核心服务到容器
├─ Phase 7: App/Loading.php 扫描并加载所有应用的 Config/Function/Model
├─ Phase 8: Web 模式加载(Middleware → Whitelist → Route → Routing)
│
▼
XiaoPHP/Routing.php(路由分发)
│
├─ 1. 从容器获取 Middleware 实例,执行 check() 认证检查
├─ 2. 解析 REQUEST_URI,去除查询字符串,转小写
├─ 3. 精确路由匹配(优先)→ Route::find()
│ └─ 命中 → dispatchRoute() → 控制器方法执行 → exit
├─ 4. 静态资源匹配(URL 含点号)→ serveStatic()
│ └─ 命中 → 输出文件 → exit
├─ 5. 自动路由分发 → dispatchAuto()
│ ├─ 解析 /应用/控制器/方法
│ ├─ 查找应用目录(大小写不敏感)
│ ├─ 查找控制器文件(大小写不敏感)
│ ├─ 加载兄弟控制器(*Base.php 优先)
│ ├─ require 控制器文件
│ ├─ 容器实例化控制器
│ ├─ 查找方法(大小写不敏感)
│ └─ 执行方法,echo 返回值 → exit
│
▼
响应输出 + 日志记录
4.2 自动加载机制
框架在 console.php Phase 1 注册了一个自定义自动加载器,按以下优先级解析类:
| 命名空间前缀 | 映射目录 |
|---|---|
XiaoPHP\System\Config\ |
XiaoPHP/System/Tools/Config/ |
XiaoPHP\System\Tools\App\ |
XiaoPHP/System/Tools/App/ |
XiaoPHP\System\Tools\encrypt\ |
XiaoPHP/System/Tools/encrypt/ |
XiaoPHP\System\ |
XiaoPHP/System/Tools/Function/ |
XiaoPHP\(其他) |
XiaoPHP/ 根目录 |
App\ |
App/ 目录 |
Controller\Scaffold\ |
Scaffold/ 目录 |
此外,Phase 2 会尝试加载 vendor/autoload.php,因此 Composer 包也可正常使用。
注意:控制器类不使用命名空间,直接通过
require_once加载,类名与文件名一致(不区分大小写)。
4.3 依赖注入容器
XiaoPHP\System\Container 是一个功能完整的 DI 容器,支持:
4.3.1 核心能力
- bind():注册服务绑定,支持闭包或类名
- singleton():注册单例服务
- make():解析服务实例,支持自动装配
- run():注册并立即解析
- has():检查服务是否已注册
- get():获取已缓存的实例
- set():手动设置实例
- clear():清除实例缓存
4.3.2 自动装配
容器通过反射(Reflection)自动解析构造函数依赖:
- 有类型提示的参数 → 递归从容器解析
- 无类型提示或内置类型 → 使用默认值,无默认值则抛异常
- 支持通过
$parameters数组手动指定参数(优先于自动解析)
4.3.3 循环依赖检测
容器维护 $resolving 数组,解析服务时标记,解析完成后移除。若检测到循环依赖,抛出 RuntimeException 并打印依赖链。
4.3.4 自动装配命名空间白名单
通过 addAutowireNamespace() 可限制只允许特定命名空间的类自动装配。默认为空,即允许所有类自动装配。
4.4 服务提供者
XiaoPHP\System\Config\ServiceProvider 在启动 Phase 6 注册以下核心服务到容器:
| 服务类 | 别名 | 类型 |
|---|---|---|
MysqlTools |
db |
单例 |
RedisTools |
redis |
单例 |
Cache |
cache |
单例 |
Logs |
logs |
单例 |
Middleware |
middleware |
单例 |
Validate |
validate |
单例 |
同时调用 View::setConfig([]) 初始化视图缓存目录。
使用示例
use XiaoPHP\System\Container; $container = Container::getInstance(); // 通过类名获取 $db = $container->make(\XiaoPHP\System\Tools\App\MysqlTools::class); // 通过别名获取 $db = $container->make('db'); // 控制器中可直接 new(控制器由容器实例化,支持构造注入) class UserController { private $db; public function __construct(\XiaoPHP\System\Tools\App\MysqlTools $db) { $this->db = $db; } }
4.5 多应用架构
App/ 目录下每个子目录都是一个独立应用,具有以下结构:
App/
├── Index/ # 应用名
│ ├── Config/ # 应用级配置(自动加载)
│ ├── Controller/ # 控制器
│ ├── Function/ # 应用级函数/类库(自动加载)
│ ├── Model/ # 数据模型(自动加载)
│ ├── View/ # 视图模板
│ └── app.json # 应用配置
app.json 配置
{
"name": "Index",
"status": "on",
"description": "status值off为关闭应用文件加载,on启用应用文件加载",
"version": "1.0.0"
}
status: "on"→ 启用,自动加载该应用的 Config/Function/Model 目录下的所有 PHP 文件status: "off"→ 关闭,不加载该应用的任何文件(控制器仍可通过路由访问,但不会预加载 Model/Function)
应用加载器(App/Loading.php)
Loading::run() 方法:
- 扫描
App/下所有子目录 - 读取每个应用的
app.json,检查status - 对启用的应用,递归扫描
Config/、Function/、Model/目录 require_once所有 PHP 文件
设计意图:Model 和 Function 类不使用命名空间,通过预加载使其在控制器中可直接
new UserModel()使用。
第五章 配置系统
5.1 配置层级
XiaoPHP 的配置分为三层:
.env(环境变量)→ Config/*.php(配置文件)→ Conf::get()(运行时读取)
- .env:存放环境相关的敏感配置(数据库密码、Redis 密码、调试开关),不纳入版本控制
- Config/*.php:框架配置文件,通过
Env::Load()读取 .env 中的值,提供默认值 - Conf::get():运行时读取配置,带静态缓存
5.2 环境变量(.env)
.env 文件位于框架根目录,格式为标准的 KEY=VALUE:
# 注释行以 # 开头 DB_HOST=127.0.0.1 DB_PORT=3306 DEBUG=false
值类型自动转换
Env::load() 会自动转换以下值:
true/false→ 布尔值null→ null- 纯数字 → int 或 float
- 带引号的值 → 去除引号
Env 类 API
use XiaoPHP\System\Config\Env; // 加载 .env 并读取指定键(第二个参数为键名) $value = Env::load(null, 'DB_HOST'); // 返回字符串或 null // 读取已加载的环境变量(带默认值) $value = Env::get('CUSTOM_KEY', 'default_value'); // 加载其他环境文件,如 .env.production Env::load('.production', 'SOME_KEY');
注意:
Env::load(null, 'KEY')第一个参数是文件后缀(null 表示.env),第二个参数是要读取的键。若键值为空字符串、null 或 false,返回 null。
5.3 配置文件(Config/*.php)
每个配置文件返回一个数组,通过 Conf::get('文件名') 读取:
// Config/App.php return [ "debug" => Env::Load(null, "DEBUG") ?? 'false', "error" => 'html', ];
Conf 类 API
use XiaoPHP\System\Config\Conf; // 读取配置(带静态缓存,第二次读取直接返回缓存) $appConfig = Conf::get('App'); // 返回数组或 null $mysqlConfig = Conf::get('Mysql'); // 清除配置缓存 Conf::clearCache('App'); // 清除单个 Conf::clearCache(); // 清除全部
5.4 配置文件清单
详见 附录 B 配置项完整清单。
第六章 路由系统
6.1 路由匹配优先级
XiaoPHP 的路由按以下优先级依次匹配,命中即终止:
1. 精确路由(自定义路由)→ 最高优先级
2. 静态资源(URL 含点号)→ 次之
3. 自动路由(约定路由)→ 兜底
6.2 精确路由(自定义路由)
在 Route/Route.php 中定义:
use XiaoPHP\System\Config\Route; // 语法:Route::add(请求方法, URL路径, 应用名, 控制器名, 方法名) Route::add("GET", "/", "Index", "Index"); // 默认方法 Main Route::add("GET", "/login", "Admin", "Auth", "Login"); Route::add("POST", "/api/user", "Api", "User", "Create"); Route::add("PUT", "/api/user/{id}", "Api", "User", "Update"); // 注意:参数不会自动解析
参数说明
| 参数 | 说明 | 默认值 |
|---|---|---|
$method |
HTTP 方法(GET/POST/PUT/DELETE 等),不区分大小写 | 必填 |
$url |
URL 路径,以 / 开头,自动转小写 |
必填 |
$controller |
应用名(App 目录下的子目录名) | 必填 |
$bootstrap |
控制器文件名(不含 .php) | 必填 |
$action |
方法名 | 'Main' |
路由注册器内部存储
路由以 METHOD:URL 为键存储,值格式为 控制器/方法:App/应用名/Controller。分发时解析此字符串。
路由 API
use XiaoPHP\System\Config\Route; Route::add("GET", "/hello", "Index", "Hello"); Route::get(); // 获取所有路由数组 Route::find("/hello", "GET"); // 查找路由,返回字符串或 null
6.3 自动路由
无需手动注册,按 URL 约定自动分发:
/应用名/控制器名/方法名
| URL 段 | 含义 | 默认值 |
|---|---|---|
| 第 1 段 | 应用名(App 目录下的子目录) | Index |
| 第 2 段 | 控制器名(Controller 目录下的文件名,不含 .php) | Main |
| 第 3 段 | 方法名 | index |
示例
| URL | 应用 | 控制器 | 方法 |
|---|---|---|---|
/ |
Index | Main | index |
/Index |
Index | Main | index |
/Index/Hello |
Index | Hello | Main |
/Index/Hello/world |
Index | Hello | world |
/Admin/User/list |
Admin | User | list |
大小写不敏感
自动路由的应用名、控制器名、方法名均不区分大小写:
- 目录查找:
Helper::findDirCaseInsensitive() - 文件查找:
Helper::findFileCaseInsensitive() - 方法查找:
Helper::findMethodCaseInsensitive()
安全过滤
自动路由对每一段 URL 进行正则过滤,只保留 a-zA-Z0-9_- 字符,防止路径遍历攻击:
$app = preg_replace("/[^a-zA-Z0-9_-]/", "", $segments[0]);
6.4 静态资源路由
当 URL 路径中包含点号(.)时,框架视为静态资源请求,从 Resources/ 目录提供文件下载:
/logo.png → Resources/logo.png
/PHPFile/xxx.jpg → Resources/PHPFile/xxx.jpg
安全机制
serveStatic() 函数实现了以下安全校验:
realpath()解析真实路径- 校验真实路径是否在
Resources/目录内(防止路径遍历) - 校验是否为文件
- 设置正确的 MIME 类型、Content-Length、Accept-Ranges
- 以附件方式下载(
Content-Disposition: attachment)
注意:当前静态资源一律以附件下载方式输出,不支持内联显示。如需图片内联显示,建议直接由 Nginx 处理
Resources/目录。
6.5 路由分发函数
dispatchRoute()
精确路由分发,执行流程:
- 解析路由字符串为
[方法, 控制器/方法, 命名空间] - 验证 HTTP 请求方法是否匹配,不匹配返回 405
- 查找控制器文件(大小写不敏感)
- 加载兄弟控制器(Base 类优先)
- require 控制器文件
- 容器实例化控制器
- 查找方法(大小写不敏感)
- 执行方法并 echo 返回值
- 记录成功日志
dispatchAuto()
自动路由分发,流程与 dispatchRoute 类似,但从 URL 解析应用/控制器/方法。
第七章 控制器
7.1 控制器规范
- 位置:
App/应用名/Controller/控制器名.php - 命名空间:无(全局命名空间)
- 类名:与文件名一致(不含 .php),首字母大写
- 方法:public 方法,可返回字符串或直接 echo
7.2 基础控制器示例
<?php /** * 用户控制器 */ class User { public function Main() { return "这是用户中心"; } public function info() { $id = $_GET['id'] ?? 0; return json_encode(['id' => $id, 'name' => '张三']); } }
7.3 控制器基类
控制器目录下的 *Base.php 文件会被优先加载(Helper::loadSiblingControllers() 按文件名含 Base 排序),可用于定义控制器基类:
<?php // App/Admin/Controller/AdminBase.php class AdminBase { public function __construct() { // 权限检查 if (!\XiaoPHP\System\Auth::check()) { header('Location: /Admin/Auth/Login'); exit; } } protected function json($data, $code = 200) { http_response_code($code); header('Content-Type: application/json'); return json_encode($data, JSON_UNESCAPED_UNICODE); } }
<?php // App/Admin/Controller/Dashboard.php class Dashboard extends AdminBase { public function Main() { return $this->json(['welcome' => '管理员']); } }
7.4 构造函数注入
控制器由 DI 容器实例化,支持构造函数类型提示自动注入:
<?php use XiaoPHP\System\Tools\App\MysqlTools; use XiaoPHP\System\Cache; class Article { private $db; private $cache; public function __construct(MysqlTools $db, Cache $cache) { $this->db = $db; $this->cache = $cache; } public function Main() { $list = $this->db->table('article')->order('id', 'DESC')->limit(10)->get(); return json_encode($list); } }
7.5 请求输入
框架不提供 Request 对象,直接使用 PHP 超全局变量:
$_GET['id'] ?? 0; // GET 参数 $_POST['title'] ?? ''; // POST 参数 $_FILES['file']; // 上传文件 $_SERVER['REQUEST_METHOD']; // 请求方法 $_SERVER['HTTP_X_REQUESTED_WITH']; // AJAX 判断
7.6 响应输出
控制器方法的返回值会被 echo 输出:
// 返回字符串 return "Hello World"; // 返回 JSON(需手动设置 Content-Type) header('Content-Type: application/json'); return json_encode(['code' => 0, 'msg' => 'success']); // 或使用 Json::encode()(自动设置 header 并 exit) \XiaoPHP\System\Json::encode(['code' => 0, 'msg' => 'success']); // 返回视图 $view = new \XiaoPHP\System\View(); return $view->set(['title' => '首页'], '')->show('index');
第八章 模型与数据库
8.1 数据库配置
在 .env 中配置:
DB_HOST=127.0.0.1 DB_PORT=3306 DB_NAME=xiaophp DB_USER=root DB_PASSWORD=123456
Config/Mysql.php 读取这些值并提供默认值。
8.2 PDO 查询构造器(MysqlTools)
XiaoPHP\System\Tools\App\MysqlTools 是框架的数据库操作核心,基于 PDO 实现,支持链式调用和预处理语句。
获取实例
use XiaoPHP\System\Container; $db = Container::getInstance()->make('db'); // 或 $db = new \XiaoPHP\System\Tools\App\MysqlTools();
8.2.1 查询数据
// 查询所有记录 $users = $db->table('user')->get(); // 带条件查询 $users = $db->table('user') ->where('status', 1) ->where('age', '>=', 18) ->order('id', 'DESC') ->limit(10) ->offset(0) ->get(); // 查询单条记录 $user = $db->table('user')->where('id', 1)->first(); // 模糊查询 $users = $db->table('user')->whereLike('name', '张')->get(); $users = $db->table('user')->whereLike('name', '张', 'left')->get(); // %张 $users = $db->table('user')->whereLike('name', '张', 'right')->get(); // 张% // 多字段模糊查询(OR) $users = $db->table('user') ->whereMultiLike(['name', 'email', 'phone'], '关键词') ->get(); // 全字段模糊查询(自动 SHOW COLUMNS) $users = $db->table('user')->whereFullLike('关键词')->get(); // IN 查询 $users = $db->table('user')->where('id', [1, 2, 3])->get(); // 统计 $count = $db->table('user')->where('status', 1)->count(); $sum = $db->table('order')->where('user_id', 1)->sum('amount'); // 获取最小/最大 ID $range = $db->table('user')->getMinMaxId(); // ['min_id' => 1, 'max_id' => 100]
8.2.2 where() 方法详解
where() 支持三种调用形式:
// 1. 两参数:where('字段', 值) → 字段 = 值 $db->table('user')->where('status', 1)->get(); // 2. 三参数:where('字段', 操作符, 值) $db->table('user')->where('age', '>=', 18)->get(); $db->table('user')->where('name', '!=', 'admin')->get(); // 3. 两参数 + 操作符嵌入值:where('字段', '>=18') $db->table('user')->where('age', '>=18')->get(); // 4. 数组值 → IN 查询 $db->table('user')->where('id', [1, 2, 3])->get();
支持的操作符:=、>=、<=、>、<、!=、LIKE(通过 whereLike)。
8.2.3 插入数据
$id = $db->table('user')->insert([ 'name' => '张三', 'email' => 'zhangsan@example.com', 'status' => 1, 'created_at' => date('Y-m-d H:i:s'), ]); // 返回插入记录的自增 ID
8.2.4 更新数据
// 必须带 WHERE 条件,否则报错 $affected = $db->table('user') ->where('id', 1) ->update([ 'name' => '李四', 'email' => 'lisi@example.com', ]); // 返回影响行数 // SQL 表达式更新(以括号开头的值被视为表达式) $db->table('user') ->where('id', 1) ->update(['login_count' => '(login_count + 1)']);
8.2.5 删除数据
// 必须带 WHERE 条件,否则报错 $affected = $db->table('user')->where('id', 1)->delete(); // 返回影响行数
8.2.6 事务
// 通过 MysqlTools 直接操作 $db->beginTransaction(); try { $db->table('account')->where('id', 1)->update(['balance' => '(balance - 100)']); $db->table('account')->where('id', 2)->update(['balance' => '(balance + 100)']); $db->commit(); } catch (\Exception $e) { $db->rollBack(); throw $e; } // 或通过 Model 基类 $model = new \XiaoPHP\System\Model(); $model->beginTransaction(); // ... $model->commit();
注意:
Model基类通过$this->db->pdo访问 PDO 实例来操作事务。
8.2.7 安全机制
- 预处理语句:所有查询值通过 PDO 预处理绑定,防止 SQL 注入
- 表名/列名白名单:
sanitizeTableName()和sanitizeColumnName()只保留a-zA-Z0-9_ - UPDATE/DELETE 必须带 WHERE:防止全表更新/删除
- 错误处理:PDO 设置
ERRMODE_EXCEPTION,数据库错误抛出异常
8.3 模型基类(Model)
XiaoPHP\System\Model 提供了 ActiveRecord 风格的基类,业务模型继承它即可获得常用 CRUD 方法。
定义模型
<?php // App/Index/Model/UserModel.php class UserModel extends \XiaoPHP\System\Model { protected $table = 'user'; // 表名 // 自定义方法 public function findByUsername($username) { return $this->db->table($this->table) ->where('username', $username) ->first(); } public function updateLogin($id, $ip) { return $this->db->table($this->table) ->where('id', $id) ->update([ 'last_login_ip' => $ip, 'last_login_time' => date('Y-m-d H:i:s'), ]); } }
内置方法
| 方法 | 说明 | 返回值 |
|---|---|---|
find($id) |
按主键 ID 查询 | 关联数组 / false |
all($where = []) |
查询所有(可带简单等值条件) | 二维数组 |
create($data) |
插入记录 | 插入 ID / false |
update($id, $data) |
按主键更新 | 影响行数 |
delete($id) |
按主键删除(硬删除) | 影响行数 |
save($data, $id = null) |
自动判断插入/更新 | ID / 影响行数 / false |
paginate($page, $size, $where = []) |
分页查询 | ['data'=>[], 'total'=>N, 'page'=>N, 'size'=>N] |
db() |
返回查询构造器实例 | MysqlTools |
getTable() |
获取表名 | string |
beginTransaction() |
开启事务 | - |
commit() |
提交事务 | - |
rollBack() |
回滚事务 | - |
使用示例
$userModel = new UserModel(); // 查询 $user = $userModel->find(1); $users = $userModel->all(['status' => 1]); // 分页 $result = $userModel->paginate(1, 10, ['status' => 1]); // 插入 $id = $userModel->create(['name' => '张三', 'status' => 1]); // 更新 $userModel->update(1, ['name' => '李四']); // 删除 $userModel->delete(1); // 复杂查询(通过 db() 获取查询构造器) $users = $userModel->db() ->where('age', '>=', 18) ->whereLike('name', '张') ->order('id', 'DESC') ->limit(10) ->get();
注意:Model 基类在构造函数中
new MysqlTools(),而非从容器获取。这意味着每个 Model 实例都会创建一个新的数据库连接(PDO 默认非持久连接)。如需共享连接,建议从容器获取MysqlTools单例。
第九章 视图引擎
9.1 视图引擎概述
XiaoPHP\System\View 是一款自研的编译型模板引擎,具有以下特性:
- 模板继承(@extends / @section / @yield)
- 模板包含(@include)
- 条件判断(@if / @elseif / @else / @endif)
- 循环(@foreach / @for / @forelse)
- 变量输出({{ $var }} 自动转义 / {{! $var }} 不转义)
- 原始 PHP(@php ... @endphp)
- 注释({{-- 注释 --}})
- 编译缓存(模板修改自动重新编译)
- HTML 压缩(非调试模式自动压缩)
- 自动注入 generator meta 标签
9.2 基本使用
<?php use XiaoPHP\System\View; class Index { public function Main() { $view = new View(); return $view->set([ 'title' => '欢迎使用 XiaoPHP', 'h1' => '轻量级 MVC 框架', 'version' => 'V2.1.3', ], '')->show('index'); } }
set() 方法参数
$view->set($data, $path = '', $debug = null);
| 参数 | 说明 |
|---|---|
$data |
分配给模板的变量数组 |
$path |
模板子目录路径(View 目录下的子目录),空字符串表示根目录 |
$debug |
单独指定调试模式(null 则使用全局配置) |
show() 方法
$view->show($filename);
$filename:模板文件名(不含 .html 扩展名),会经过basename()和正则过滤- 返回渲染后的 HTML 字符串
9.3 模板查找规则
视图引擎通过 debug_backtrace() 获取调用者文件路径,从而确定当前应用的 View 目录:
调用者文件:App/Index/Controller/Index.php
→ 向上两级目录:App/Index/
→ View 目录:App/Index/View/
→ 模板文件:App/Index/View/{path}/{filename}.html
这意味着视图是按应用隔离的,每个应用的控制器只能渲染自己 View 目录下的模板。
9.4 模板语法
9.4.1 变量输出
<!-- 自动 HTML 转义(推荐) --> <h1>{{ $title }}</h1> <!-- 不转义(用于输出 HTML) --> <div>{{! $content }}</div> <!-- 数组访问(点语法或方括号) --> <p>{{ $user.name }}</p> <p>{{ $user['name'] }}</p> <!-- 对象属性访问 --> <p>{{ $user->name }}</p>
变量路径解析支持链式访问:
$user.profile.name会被编译为$user["profile"]["name"] ?? (is_object($user) ? $user->profile->name : null),每一级都有空合并运算符,避免未定义索引报错。
9.4.2 条件判断
@if ($user['role'] === 'admin')
<p>管理员</p>
@elseif ($user['role'] === 'editor')
<p>编辑</p>
@else
<p>普通用户</p>
@endif
支持嵌套条件,
@if的条件表达式支持括号嵌套。
9.4.3 循环
<!-- foreach --> @foreach ($users as $user) <li>{{ $user.name }}</li> @endforeach <!-- foreach with key --> @foreach ($users as $id => $user) <li>{{ $id }}: {{ $user.name }}</li> @endforeach <!-- forelse(空数据有默认输出) --> @forelse ($users as $user) <li>{{ $user.name }}</li> @empty <li>暂无数据</li> @endforelse <!-- for 循环 --> @for ($i = 0; $i < 10; $i++) <span>{{ $i }}</span> @endfor
9.4.4 模板继承
布局模板 layout.html:
<!DOCTYPE html> <html> <head> <title>@yield('title')</title> </head> <body> <header>公共头部</header> <main>@yield('content')</main> <footer>公共底部</footer> </body> </html>
子模板 page.html:
@extends('layout')
@section('title')
页面标题
@endsection
@section('content')
<h1>页面内容</h1>
@endsection
@extends('layout')中的布局名用点号分隔目录,如@extends('layouts.main')对应View/layouts/main.html。
9.4.5 模板包含
@include('header') <!-- 包含 View/header.html -->
@include('partials.nav') <!-- 包含 View/partials/nav.html -->
被包含的模板会继承当前模板的所有变量。
9.4.6 原始 PHP
@php
$now = date('Y-m-d H:i:s');
echo "当前时间:$now";
@endphp
9.4.7 注释
{{-- 这是模板注释,不会输出到 HTML --}}
9.5 编译缓存
模板编译后缓存到 Temp/View/ 目录,文件名以模板文件路径的 md5 值命名:
Temp/View/
├── a1b2c3d4e5f6...php <!-- 编译后的模板 -->
└── ...
缓存失效条件:模板文件的修改时间晚于编译缓存文件的修改时间。
9.6 HTML 压缩
非调试模式下,视图引擎会自动压缩输出的 HTML:
- 删除标签间多余空白
- 合并多个空格为一个
- 删除 HTML 注释
以下标签内容受保护不被压缩:pre、textarea、script、style,以及内联 style 属性和 on* 事件属性。
9.7 自动转义控制
$view = new View(); // 关闭自动转义(全局) $view->setAutoEscape(false); // 或在模板中使用 {{! $var }} 单个不转义
默认开启自动转义,
{{ $var }}会编译为htmlspecialchars($var, ENT_QUOTES, 'UTF-8')。
9.8 视图配置
use XiaoPHP\System\View; View::setConfig([ 'cache_dir' => '/custom/cache/path', // 编译缓存目录 'debug' => false, // 调试模式(不压缩 HTML) 'minify_html' => true, // 是否压缩 HTML 'preserve_tags' => ['pre', 'textarea', 'script', 'style'], // 保护标签 'asset_meta' => [ 'name' => 'generator', 'content' => 'MyApp v1.0', ], ]);
第十章 中间件与认证
XiaoPHP 提供两套独立的认证机制:
- Token 中间件(
Middleware类):基于 Token 的无状态认证,适用于 API - Session 认证(
Auth类):基于 Session 的有状态认证,适用于后台管理
10.1 Token 中间件
10.1.1 工作原理
中间件在路由分发前执行 Middleware::check(),对所有非白名单路由进行 Token 认证:
- 检查当前 URL 是否在白名单中,是则跳过认证
- 按配置的认证模式依次尝试获取 Token
- 从存储(文件缓存或 Redis)中验证 Token 是否存在
- 验证失败返回 401
10.1.2 配置
Config/Middleware.php:
return [ "storage" => 'cache', // 存储方式:cache | redis "token_key" => 'token', // Token 参数名(POST/GET 方式) "auth_mode" => 'bearer,post,cookie', // 认证方式(逗号分隔,按顺序尝试) "cookie_name" => 'auth_token', // Cookie 名称 "cookie_expire" => '7200', // 默认过期时间(秒) ];
10.1.3 认证模式
| 模式 | 获取方式 | 说明 |
|---|---|---|
bearer |
Authorization: Bearer <token> 请求头 |
推荐,最安全 |
post |
$_POST['token'] |
适用于表单提交 |
cookie |
$_COOKIE['auth_token'] |
适用于 Web 页面 |
get |
$_GET['token'] |
不推荐,Token 会出现在 URL/Referer/历史记录中 |
10.1.4 Token 管理
use XiaoPHP\System\Container; $middleware = Container::getInstance()->make('middleware'); // 设置 Token(登录成功后调用) $token = bin2hex(random_bytes(32)); $middleware->setToken($token, [ 'user_id' => 1, 'username' => 'admin', ], 7200); // 过期时间秒,默认 7200 // 删除 Token(登出) $middleware->delToken($token);
10.1.5 存储机制
- 文件缓存:Token 以
token_{md5(token)}为键存储在Temp/Cache/目录 - Redis:Token 以
token_{md5(token)}为键存储在 Redis,Redis 不可用时自动回退到文件缓存
10.2 白名单
Whitelist/Whitelist.php 中定义免认证路由:
use XiaoPHP\System\Config\Whitelist; Whitelist::add("/"); Whitelist::add("/Admin/Auth/Login"); Whitelist::add("/Api/Public/*"); // 通配符
白名单 API
Whitelist::add($url); // 添加 Whitelist::remove($url); // 移除 Whitelist::clear(); // 清空 Whitelist::exists($url); // 判断是否存在 Whitelist::check($url); // 检查 URL 是否匹配(支持通配符 *) Whitelist::get(); // 获取所有白名单
通配符匹配
白名单 URL 中的 * 会被转换为正则 .*,支持前缀匹配:
/Api/Public/*匹配/Api/Public/user、/Api/Public/article/list等
安全警告:不要使用
/*通配符,否则会完全绕过中间件认证。
10.3 Session 认证(Auth 类)
XiaoPHP\System\Auth 提供基于 Session 的后台认证,内置角色权限和 CSRF 保护。
10.3.1 用户模型约定
Auth 类假设存在 UserModel 类(需在 App/应用/Model/ 中定义),具有以下方法和字段:
class UserModel extends \XiaoPHP\System\Model { protected $table = 'admin_user'; public function findByUsername($username) { return $this->db->table($this->table) ->where('username', $username) ->first(); } public function updateLogin($id, $ip) { return $this->db->table($this->table) ->where('id', $id) ->update([ 'last_login_ip' => $ip, 'last_login_time' => date('Y-m-d H:i:s'), ]); } }
用户表需包含字段:id、username、password、salt、status、nickname、email、role、avatar。
10.3.2 登录
use XiaoPHP\System\Auth; $result = Auth::attempt($username, $password); if ($result['success']) { // 登录成功,自动创建 Session header('Location: /Admin/Dashboard/Main'); exit; } else { echo $result['message']; // 用户不存在 / 账号已被禁用 / 密码错误 }
密码校验逻辑:password_verify($password . $salt, $stored_hash),即密码加盐后使用 password_hash() 存储。
10.3.3 直接登录
Auth::login($userArray); // 直接以指定用户登录(如第三方回调后)
10.3.4 登出
Auth::logout(); // 清空 Session、删除 Cookie、销毁会话
10.3.5 登录状态与用户信息
Auth::check(); // 是否已登录,返回 bool Auth::user(); // 当前用户数组,未登录返回 null Auth::id(); // 当前用户 ID,未登录返回 0 Auth::role(); // 当前角色,默认 'guest'
10.3.6 角色权限
内置角色等级:super_admin(3) > admin(2) > editor(1)
Auth::hasRole('admin'); // 是否恰好是 admin 角色 Auth::atLeast('admin'); // 是否至少是 admin 等级 Auth::requireLogin(); // 要求登录,未登录跳转 /Admin/Auth/Login Auth::requireRole('admin'); // 要求至少 admin 权限,不足返回 403
10.3.7 密码加密
$salt = Auth::generateSalt(); // 生成 32 位十六进制盐值 $hash = Auth::hashPassword($password, $salt); // 加密密码(加盐后 password_hash)
10.3.8 CSRF 保护
// 生成 CSRF Token(存入 Session) $token = Auth::csrfToken(); // 表单中使用 echo '<input type="hidden" name="_csrf" value="' . $token . '">'; // 校验 CSRF Token(双 Token 机制,支持浏览器二次请求) if (!Auth::checkCsrf($_POST['_csrf'] ?? null)) { Error(419, 'CSRF 验证失败'); }
CSRF 采用双 Token 机制:
- 每次校验成功后轮换 Token(当前变为上一个,生成新的当前)
- 当前 Token 和上一个 Token 都有效,解决浏览器自动二次请求导致的 Token 不一致问题
- 使用
hash_equals()防时序攻击
10.3.9 Session 安全
Auth 类在启动 Session 时设置:
session.cookie_httponly = 1(防止 XSS 窃取)session.cookie_samesite = Lax(防止 CSRF)- 登录成功后
session_regenerate_id(true)(防止会话固定)
第十一章 缓存系统
11.1 文件缓存(Cache 类)
XiaoPHP\System\Cache 提供基于文件的缓存,使用 serialize 序列化数据。
配置
Config/Cache.php:
return [ "dir" => __DIR__ . '/../Temp/Cache/', // 缓存目录 "expire" => '3600', // 默认过期时间(秒) ];
API
use XiaoPHP\System\Container; $cache = Container::getInstance()->make('cache'); // 设置缓存 $cache->set('key', ['data' => 'value'], 3600); // 第三个参数为过期时间,默认 3600 秒 // 获取缓存 $data = $cache->get('key'); // 返回数据或 null // 删除缓存 $cache->delete('key'); // 清空所有缓存 $cache->clear();
存储机制
- 缓存文件以
md5($key) . '.txt'命名 - 文件内容为
serialize(['expire' => 时间戳, 'data' => 数据]) - 获取时检查过期时间,过期则删除文件并返回 null
- 写入时使用
LOCK_EX独占锁,防止并发写入冲突
11.2 Redis 缓存
详见 第十七章 Redis 操作。中间件的 Token 存储可切换为 Redis。
第十二章 日志系统
12.1 日志配置
Config/Logs.php:
return [ "success" => 'true', // 是否记录成功日志 "error" => 'true', // 是否记录错误日志 ];
12.2 日志记录
框架在路由分发完成后自动记录日志:
- 成功请求:
Logs::logs(0, 200) - 错误请求:
Logs::logs(1, 404/405/401/...)
也可手动记录:
use XiaoPHP\System\Container; $logs = Container::getInstance()->make('logs'); $logs->logs(0, 200); // 成功日志 $logs->logs(1, 500); // 错误日志
12.3 日志格式
日志文件按日期存储:
logs/
├── success/
│ └── 2026-08-26.logs
└── error/
└── 2026-08-26.logs
每行日志格式:
success 2026-08-26 14:30:00--[192.168.1.1]:/Index/Index/Main-code:200
12.4 安全特性
- IP 获取:优先读取
X-Forwarded-For(取第一个 IP),其次HTTP_CLIENT_IP,最后REMOTE_ADDR - 日志注入防护:请求路径经过
urldecode后移除所有控制字符(\x00-\x1f\x7f)和换行符,防止日志注入 - 并发安全:使用
FILE_APPEND | LOCK_EX追加写入
第十三章 数据验证器
13.1 验证器概述
XiaoPHP\System\Validate 提供 16 种常用数据验证规则,全部为静态方法。验证通过返回原始数据,失败返回 false。
13.2 验证规则清单
| 方法 | 说明 | 示例 |
|---|---|---|
phone($data) |
中国大陆手机号(1开头,3-9第二位,11位) | Validate::phone('13800138000') |
email($data) |
邮箱地址 | Validate::email('a@b.com') |
username($data) |
用户名(2-20位,字母数字下划线中文) | Validate::username('张三123') |
password($data) |
密码(6-20位,至少含字母和数字) | Validate::password('abc123') |
idCard($data) |
18位身份证号(最后一位可为X) | Validate::idCard('110101199001011234') |
url($data) |
URL(http/https) | Validate::url('https://example.com') |
ip($data) |
IPv4 或 IPv6 | Validate::ip('192.168.1.1') |
date($data) |
日期(YYYY-MM-DD) | Validate::date('2026-08-26') |
numeric($data) |
数字(整数或浮点数) | Validate::numeric('123.45') |
between($data, $min, $max) |
数字范围(闭区间) | Validate::between(5, 1, 10) |
length($data, $min, $max) |
字符串长度(UTF-8,中文算1字符) | Validate::length('你好', 1, 10) |
time($data) |
时间(HH:MM:SS 或 HH:MM) | Validate::time('14:30:00') |
postcode($data) |
中国邮政编码(6位数字) | Validate::postcode('100000') |
qq($data) |
QQ号(5-12位,不以0开头) | Validate::qq('123456789') |
13.3 使用示例
use XiaoPHP\System\Validate; // 单个验证 if (Validate::phone($_POST['phone'])) { // 手机号合法 } // 批量验证 $errors = []; if (!Validate::username($_POST['username'] ?? '')) { $errors[] = '用户名格式不正确'; } if (!Validate::password($_POST['password'] ?? '')) { $errors[] = '密码需6-20位且包含字母和数字'; } if (!Validate::email($_POST['email'] ?? '')) { $errors[] = '邮箱格式不正确'; } if (!empty($errors)) { // 验证失败 }
第十四章 文件管理
14.1 文件上传
XiaoPHP\System\File 提供安全的文件上传功能,存储目录为 Resources/PHPFile/。
基本使用
use XiaoPHP\System\File; $file = new File(); // 上传(表单字段名必须为 file) $result = $file->upload( ['image/jpeg', 'image/png', 'image/gif'], // 允许的 MIME 类型 'avatar', // 子目录(Resources/PHPFile/avatar/) true // 是否随机命名 ); if ($result['code'] === 0) { echo "上传成功:" . $result['filename']; } else { echo "上传失败:" . $result['msg']; }
upload() 参数
| 参数 | 说明 | 默认值 |
|---|---|---|
$allowedTypes |
允许的 MIME 类型数组,空数组表示不限制 | [] |
$subdir |
存储子目录 | '' |
$randomName |
是否使用随机文件名(32位十六进制+扩展名) | true |
返回值
// 成功 ['code' => 0, 'msg' => '上传成功', 'filename' => 'a1b2c3...jpg', 'metadata' => '/path/原文件名.json'] // 失败 ['code' => 1, 'msg' => '失败原因']
14.2 安全机制
文件上传实现了多重安全防护:
- 文件大小限制:默认最大 2MB(
$maxSize = 2 * 1024 * 1024) - MIME 类型校验:使用
finfo_open(FILEINFO_MIME_TYPE)检测文件真实 MIME(非扩展名) - PHP 代码扫描:读取文件前 4096 字节,检测是否包含
<?php、<?=、<script language=php>等标签,防止上传 Webshell - 随机文件名:默认使用
bin2hex(random_bytes(16))生成随机文件名,避免路径遍历和覆盖 - 文件名过滤:非随机命名时,文件名只保留
a-zA-Z0-9_\-.字符 - 目录校验:存储目录不存在时自动创建
14.3 文件操作
$file = new File(); // 获取文件信息(大小、修改时间、MIME、MD5、元数据) $info = $file->getInfo('avatar/a1b2c3.jpg'); // 返回 ['code'=>0, 'info'=>['size'=>..., 'mtime'=>..., 'mime'=>..., 'md5'=>..., 'metadata'=>...]] // 读取文件内容 $content = $file->read('avatar/a1b2c3.jpg'); // 删除文件(同时删除关联的 .json 元数据文件) $result = $file->delete('avatar/a1b2c3.jpg');
14.4 元数据文件
随机命名上传时,会在同目录下生成一个以原始文件名命名的 .json 元数据文件:
{
"filename": "a1b2c3d4e5f6...jpg",
"md5": "d41d8cd98f00b204e9800998ecf8427e"
}
删除文件时会自动查找并删除对应的元数据文件。
14.5 路径安全
所有文件操作通过 resolvePath() 方法解析路径:
- 拼接存储根目录和相对路径
realpath()解析真实路径- 校验真实路径是否以存储根目录开头(防止路径遍历
../../etc/passwd)
第十五章 HTTP 客户端
15.1 Wget 类
XiaoPHP\System\Wget 基于 cURL 封装,支持 GET/POST/PUT/DELETE/Download 五种请求方式。
GET 请求
use XiaoPHP\System\Wget; $response = Wget::get('https://api.example.com/data'); // 成功返回响应体字符串 // 失败返回 ['error' => '错误信息', 'http_code' => 0] // 带 SSL 验证和自定义选项 $response = Wget::get('https://api.example.com/data', true, [ CURLOPT_HTTPHEADER => ['Authorization: Bearer token'], CURLOPT_TIMEOUT => 10, ]);
POST 请求
// 表单数据(数组自动 http_build_query) $response = Wget::post('https://api.example.com/login', [ 'username' => 'admin', 'password' => '123456', ]); // JSON 数据(字符串自动设置 Content-Type: application/json) $response = Wget::post('https://api.example.com/api', json_encode(['key' => 'value']));
PUT / DELETE 请求
$response = Wget::put('https://api.example.com/user/1', ['name' => '李四']); $response = Wget::delete('https://api.example.com/user/1');
文件下载
$result = Wget::download('https://example.com/file.zip', '/path/to/save.zip'); // 成功返回 true // 失败返回 ['error' => '错误信息', 'http_code' => 0] // 超时时间 300 秒
通用参数
所有方法都支持以下参数:
| 参数 | 说明 | 默认值 |
|---|---|---|
$url |
请求 URL | 必填 |
$ssl |
是否验证 SSL 证书 | false |
$options |
自定义 cURL 选项数组 | [] |
安全提示:默认
$ssl = false不验证 SSL 证书,生产环境调用 HTTPS 接口建议设为true。
15.2 Json 类
XiaoPHP\System\Json 提供 JSON 相关的快捷方法。
JSON 输出
use XiaoPHP\System\Json; // 直接输出 JSON 并 exit(自动设置 Content-Type: application/json) Json::encode(['code' => 0, 'msg' => 'success', 'data' => [...]])
JSON 解码
$data = Json::decode('{"key":"value"}'); // 返回关联数组
远程 JSON 获取
// 请求远程 URL 并解析 JSON 响应 $data = Json::wdecode('https://api.example.com/data.json'); // 成功返回解析后的数组 // 失败返回 ['error' => '错误信息']
第十六章 加密工具
16.1 AES 加密(AesTool)
XiaoPHP\System\AesTool 提供 AES 加解密,使用 SHA1PRNG 算法派生密钥(兼容 Java 端的 SecureRandom SHA1PRNG 实现)。
use XiaoPHP\System\AesTool; $key = 'my-secret-key'; $data = 'Hello World'; // 加密(默认 AES-128-ECB) $encrypted = AesTool::encode($data, $key); // 返回 Base64 编码的密文 // 解密 $decrypted = AesTool::decode($encrypted, $key); // 返回原始明文 // 指定算法和 IV $encrypted = AesTool::encode($data, $key, 'AES-256-CBC', '16-byte-iv-here'); $decrypted = AesTool::decode($encrypted, $key, 'AES-256-CBC', '16-byte-iv-here');
密钥派生
_sha1prng() 方法通过迭代 SHA1 哈希生成指定长度的密钥:
- 首次哈希:
sha1(key) - 后续哈希:
sha1(上一次哈希结果) - 拼接直到达到目标长度,截取前 N 字节
目标长度从算法名中提取(如 AES-128-ECB → 128 bit → 16 字节),默认 16 字节。
注意:ECB 模式不安全(相同明文产生相同密文),生产环境建议使用 CBC 或 GCM 模式并提供 IV。
16.2 RSA 加密(RSATool)
XiaoPHP\System\RSATool 提供 RSA 加解密,使用 OAEP 填充。
use XiaoPHP\System\RSATool; $rsa = new RSATool(); // 公钥加密($pubKey 为纯 Base64 字符串,不含 PEM 头尾) $encrypted = $rsa->encode('敏感数据', $publicKeyBase64); // 返回 Base64 编码的密文 // 私钥解密($privateKey 为完整 PEM 格式私钥) $decrypted = $rsa->decode($encrypted, $privateKeyPem); // 返回原始明文
公钥格式
encode() 方法接受的公钥为纯 Base64 字符串(不含 -----BEGIN PUBLIC KEY----- 头尾),内部会自动包装为 PEM 格式。
私钥格式
decode() 方法接受的私钥为完整 PEM 格式字符串(包含 -----BEGIN PRIVATE KEY----- 头尾)。
注意:RSA 加密有长度限制(1024 位密钥最多加密 117 字节,2048 位最多 245 字节),适合加密短数据或对称密钥。加密大量数据应使用「RSA 加密 AES 密钥 + AES 加密数据」的混合加密方案。
第十七章 Redis 操作
17.1 Redis 配置
.env:
REDIS_HOST=127.0.0.1 REDIS_PORT=6379 REDIS_PASSWORD=
17.2 RedisTools 类
XiaoPHP\System\Tools\App\RedisTools 封装了 phpredis 扩展,具有以下特性:
- 自动降级:Redis 扩展未安装或连接失败时,所有操作静默返回
$this(链式调用不报错) - 魔术方法:通过
__call()转发所有 Redis 命令到 phpredis 实例 - 管道支持:
pipeline()/exec()
获取实例
use XiaoPHP\System\Container; $redis = Container::getInstance()->make('redis');
基本操作
// 字符串 $redis->set('key', 'value', 3600); // 设置(带过期时间) $value = $redis->get('key'); // 获取 $redis->del('key'); // 删除 $redis->exists('key'); // 是否存在 $redis->incr('counter'); // 自增 $redis->expire('key', 3600); // 设置过期 // 哈希 $redis->hSet('user:1', 'name', '张三'); $name = $redis->hGet('user:1', 'name'); $all = $redis->hGetAll('user:1'); // 列表 $redis->lPush('queue', 'task1'); $task = $redis->rPop('queue'); // 集合 / 有序集合 $redis->sAdd('tags', 'php'); $redis->zAdd('rank', 100, 'user1');
由于使用
__call()魔术方法,所有 phpredis 支持的命令都可直接调用,方法名与 Redis 命令一致。
管道(Pipeline)
$result = $redis->pipeline() ->set('key1', 'value1') ->set('key2', 'value2') ->get('key1') ->exec(); // 返回所有命令的结果数组
可用性检查
if ($redis->isAvailable()) { // Redis 可用 } else { // Redis 不可用,使用文件缓存降级 }
17.3 连接参数
- 主机:从配置读取,默认
127.0.0.1 - 端口:从配置读取,默认
6379 - 连接超时:2.5 秒
- 密码:配置非空时调用
auth()认证 - 析构时自动
close()关闭连接
第十八章 阿里云 DNS
18.1 概述
XiaoPHP\System\Tools\App\AliyunDns 封装了阿里云云解析 DNS API(2015-01-09 版本),支持域名记录的增删改查。
注意:该类的命名空间为
XiaoPHP\app\tools(小写 app),与其他核心类的XiaoPHP\System\不同,使用时需注意。
18.2 配置
Config/AliyunDns.php:
return [ "accessKeyId" => 'your-access-key-id', "accessKeySecret" => 'your-access-key-secret', ];
18.3 API 方法
查询域名列表
$dns = new \XiaoPHP\app\tools\AliyunDns(); // 查询所有域名 $result = $dns->domains(); // 返回 ['code'=>200, 'data'=>[...], 'total'=>N] // 查询指定域名的解析记录 $result = $dns->list('example.com');
添加解析记录
$result = $dns->add([ 'domain' => 'example.com', // 域名 'rr' => 'www', // 主机记录 'record' => 'A', // 记录类型:A/AAAA/CNAME/MX/TXT/NS/SRV 'value' => '1.2.3.4', // 记录值 'ttl' => 600, // TTL,默认 600 'priority' => 10, // MX 记录优先级(可选) 'line' => 'default', // 解析线路(可选) ]); // 成功返回 ['code'=>200, 'message'=>'Record added successfully', 'data'=>['recordId'=>..., 'requestId'=>...]]
查询解析记录
$result = $dns->get([ 'domain' => 'example.com', 'rr' => 'www', 'record' => 'A', ]); // 单条返回 ['code'=>200, 'data'=>[...]] // 多条返回 ['code'=>200, 'data'=>[...], 'total'=>N] // 未找到返回 ['code'=>404, 'message'=>'Record not found']
更新解析记录
$result = $dns->update([ 'recordId' => '12345', // 记录 ID(必填) 'rr' => 'www', // 可选 'record' => 'A', // 可选 'value' => '2.3.4.5', // 可选 'ttl' => 600, // 可选 ]);
删除解析记录
// 方式一:通过 recordId 删除 $result = $dns->del(['recordId' => '12345']); // 方式二:通过 domain + rr 自动查找 recordId 后删除 $result = $dns->del([ 'domain' => 'example.com', 'rr' => 'www', ]);
启用/禁用解析记录
$result = $dns->setStatus([ 'recordId' => '12345', 'status' => 'disable', // enable | disable ]);
设置备注
$result = $dns->remark([ 'recordId' => '12345', 'remark' => '服务器A', ]);
18.4 签名机制
API 请求使用阿里云标准的 HMAC-SHA1 签名:
- 合并公共参数和业务参数
- 按参数名字典排序
- 构造规范化查询字符串
- 构造待签名字符串:
GET&%2F&{urlencode(查询字符串)} - 使用
accessKeySecret + &作为密钥,HMAC-SHA1 签名后 Base64 编码
第十九章 错误处理与调试
19.1 错误处理体系
XiaoPHP 的错误处理由三个文件协同工作:
| 文件 | 作用 |
|---|---|
XiaoPHP/start.php |
注册错误/异常/致命错误处理器 |
XiaoPHP/debug.php |
调试信息渲染(异常详情页) |
XiaoPHP/System/Error/error.php |
全局 Error() 函数(HTTP 错误响应) |
19.2 错误处理注册(start.php)
在启动 Phase 3 注册三个处理器:
- register_shutdown_function:捕获致命错误(E_ERROR、E_PARSE、E_CORE_ERROR、E_COMPILE_ERROR),转为
ErrorException并调用displayDebugInfo() - set_error_handler:将所有 PHP 错误(warning、notice 等)转为
ErrorException抛出 - set_exception_handler:捕获未处理的异常,调用
displayDebugInfo()
19.3 调试信息页(debug.php)
当 Config/App.php 中 debug = true 时,异常会渲染为详细的调试页面,包含:
- 异常类型和消息
- 出错文件和行号
- 代码片段(出错行前后各 7 行,高亮出错行)
- 完整调用堆栈
- 请求上下文(GET/POST/SESSION/COOKIE/FILES/SERVER,可折叠)
- 客户端 IP、PHP 版本、当前时间
- 返回 / 首页按钮
当 debug = false 时:
- 异常消息写入
error_log - 返回 500 状态码
- 显示「系统繁忙,请稍后再试」的简洁页面
19.4 全局 Error() 函数
Error($code, $info) 用于输出 HTTP 错误响应:
Error(404, '页面不存在'); Error(401, '未授权'); Error(403, '权限不足'); Error(405, '请求方法不允许'); Error(500, '服务器内部错误');
响应格式
由 Config/App.php 的 error 配置决定:
- html 格式:查找
XiaoPHP/System/Error/{code}.html并包含输出,变量$code和$info在模板中可用 - json 格式(默认):输出 JSON
{
"code": 404,
"date": "2026-08-26 14:30:00",
"data": {
"msg": "页面不存在"
}
}
19.5 自定义错误页
框架内置 401、403、404 三个 HTML 错误页,可直接编辑 XiaoPHP/System/Error/ 下的对应文件自定义样式。
如需新增错误码页面(如 500.html),创建对应文件即可,Error(500, ...) 会自动查找。
19.6 调试模式开启
编辑 .env:
DEBUG=true
或直接编辑 Config/App.php:
return [ "debug" => 'true', // 开启调试 "error" => 'html', ];
生产环境必须关闭调试模式,否则会泄露代码、堆栈、服务器环境等敏感信息。
第二十章 CLI 命令行
20.1 CLI 入口(XiaoCTL)
XiaoCTL 是框架的命令行入口文件,位于框架根目录:
php XiaoCTL
它定义了 CTL 常量,使 console.php 跳过 Web 模式的中间件/路由加载,然后加载 Scaffold.php 调度器。
20.2 脚手架调度器(Scaffold.php)
XiaoPHP/Scaffold.php 解析命令行参数,调用 Controller\Scaffold\ 命名空间下的类方法:
# 语法:php XiaoCTL 类名:方法名 [参数] php XiaoCTL System:hello "Hello World"
执行流程:
- 解析
类名:方法名 - 类名和方法名经过正则过滤(只保留
a-zA-Z0-9_) - 拼接为
\Controller\Scaffold\{类名} - 检查类和方法是否存在
- 静态调用方法,传入参数
- 输出返回值
20.3 创建脚手架命令
在 Scaffold/ 目录下创建 PHP 文件:
<?php // Scaffold/Dbtools.php namespace Controller\Scaffold; class Dbtools { public static function migrate($param) { // 数据库迁移逻辑 echo "迁移完成: $param\n"; } public static function seed($param) { // 数据填充逻辑 echo "填充完成: $param\n"; } }
使用:
php XiaoCTL Dbtools:migrate users php XiaoCTL Dbtools:seed admin
20.4 应用创建命令(App/Loading.php)
App/Loading.php 同时支持 CLI 模式创建新应用:
php App/Loading.php add
进入交互式命令行:
XiaoPHP V 2.1.0
命令模式:创建应用-输入Exit退出
请输入应用名称:Admin
已生成默认配置文件: /path/App/Admin/app.json
请输入应用名称:Exit
退出程序。
创建的应用目录结构:
App/Admin/
├── Config/
├── Controller/
├── Function/
├── Model/
├── View/
└── app.json
app.json 默认内容:
{
"name": "Admin",
"status": "on",
"description": "status值off为关闭应用文件加载,on启用应用文件加载",
"version": "1.0.0"
}
第二十一章 安全审计
21.1 安全评估总览
| 安全维度 | 评级 | 说明 |
|---|---|---|
| SQL 注入 | 优 | 全部 PDO 预处理,表名/列名白名单 |
| XSS | 良 | 视图默认自动转义,但控制器直接 echo 需自行处理 |
| CSRF | 良 | Auth 类内置双 Token CSRF,但 Token 中间件模式无 CSRF |
| 文件上传 | 优 | MIME 检测 + PHP 代码扫描 + 随机文件名 + 路径校验 |
| 路径遍历 | 优 | 静态资源和文件管理均有 realpath 校验 |
| 认证安全 | 良 | Session 认证较完善;Token 认证无刷新机制、无速率限制 |
| 密码存储 | 优 | password_hash + 盐值 |
| 会话安全 | 优 | HttpOnly + SameSite + session_regenerate_id |
| 日志注入 | 优 | 请求路径移除控制字符 |
| 信息泄露 | 中 | 调试模式关闭时安全;.env 需确保 Nginx 禁止访问 |
| SSL 验证 | 中 | HTTP 客户端默认不验证 SSL 证书 |
| 依赖安全 | 优 | 零 Composer 依赖,无供应链风险 |
21.2 已发现的安全问题与建议
问题 1:Token 中间件无速率限制和暴力破解防护
位置:XiaoPHP/Middleware.php
描述:Token 验证失败直接返回 401,无失败计数和 IP 封禁机制,可能遭受暴力枚举攻击。
建议:
// 在 check() 中添加失败计数 $failKey = 'login_fail_' . md5(Ipaddr::get()); $failCount = (int)$this->cache->get($failKey); if ($failCount >= 10) { \Error(429, '请求过于频繁,请稍后再试'); } // 验证失败时累加 if (!$valid) { $this->cache->set($failKey, $failCount + 1, 300); \Error(401, '令牌无效或已过期'); }
问题 2:HTTP 客户端默认不验证 SSL
位置:XiaoPHP/System/Tools/Function/Wget.php, Json.php
描述:$ssl 参数默认为 false,即 CURLOPT_SSL_VERIFYPEER = false,存在中间人攻击风险。
建议:生产环境调用外部 API 时显式传入 true:
Wget::get('https://api.example.com', true);
问题 3:静态资源强制下载
位置:XiaoPHP/Routing.php → serveStatic()
描述:所有静态资源都设置 Content-Disposition: attachment,图片等资源无法在浏览器内联显示。
建议:根据 MIME 类型决定是否内联:
$inlineMimes = ['image/png', 'image/jpeg', 'image/gif', 'image/webp', 'application/pdf']; if (in_array($mime, $inlineMimes)) { header('Content-Disposition: inline; filename="' . basename($path) . '"'); } else { header('Content-Disposition: attachment; filename="' . basename($path) . '"'); }
问题 4:Model 基类每次新建数据库连接
位置:XiaoPHP/System/Tools/Function/Model.php
描述:Model::__construct() 中 new MysqlTools(),而非从容器获取单例。每个 Model 实例创建一个新 PDO 连接,高并发下可能耗尽数据库连接。
建议:
public function __construct() { $this->db = Container::getInstance()->make('db'); }
问题 5:AES 默认使用 ECB 模式
位置:XiaoPHP/System/Tools/encrypt/AesTool.php
描述:默认算法为 AES-128-ECB,ECB 模式相同明文产生相同密文,不安全。
建议:使用 CBC 或 GCM 模式并提供随机 IV,将 IV 与密文一起存储/传输。
问题 6:.env 文件可能被直接访问
描述:.env 位于框架根目录,如果 Web 根目录配置错误(指向框架根目录而非 Public/),.env 可被直接下载。
建议:
- 确保 Nginx
root指向Public/目录 - 在 Nginx 配置中添加:
location ~ /\.env { deny all; }
问题 7:日志中 X-Forwarded-For 可伪造
位置:XiaoPHP/System/Tools/Function/Logs.php, Ipaddr.php
描述:直接信任 X-Forwarded-For 头的第一个 IP,客户端可伪造此头进行日志污染或绕过 IP 限制。
建议:在可信代理后面使用时,应从右往左跳过已知代理 IP,取第一个非代理 IP。
21.3 安全最佳实践清单
- 生产环境关闭调试模式(
DEBUG=false) - Nginx root 指向
Public/,禁止访问.env和隐藏文件 - 数据库使用强密码,最小权限原则
- Redis 设置密码,禁止外网访问
- 上传目录禁止执行 PHP(Nginx 配置
location ~* \.php$ { deny all; }应用于 Resources 目录) - Session Cookie 设置
secure(HTTPS 环境) - 定期清理
Temp/Cache/、Temp/View/、logs/目录 - 外部 API 调用启用 SSL 验证
- Token 认证添加速率限制
第二十二章 性能分析
22.1 性能特性
| 特性 | 说明 |
|---|---|
| 自定义自动加载 | 无 Composer 开销,类映射直接 require |
| 配置静态缓存 | Conf::get() 第二次读取直接返回静态缓存 |
| 视图编译缓存 | 模板编译为 PHP 文件,修改才重新编译 |
| 视图 HTML 压缩 | 非调试模式自动压缩,减少传输体积 |
| 服务单例 | 数据库/Redis/缓存/日志等核心服务在容器中为单例 |
| 数据库预处理 | PDO 预处理,支持查询缓存 |
| Redis 可选 | 高并发场景可切换 Token 存储到 Redis |
22.2 性能瓶颈分析
瓶颈 1:应用加载器扫描所有文件
App/Loading.php 在每次请求时递归扫描所有启用应用的 Config/、Function/、Model/ 目录并 require_once 所有 PHP 文件。应用数量多或文件多时,启动开销较大。
优化建议:
- 使用类Map缓存(将文件路径映射序列化到缓存文件)
- 或改用 Composer 自动加载(框架已支持 vendor/autoload.php)
瓶颈 2:Model 基类非单例数据库连接
如安全审计所述,每个 Model 实例创建新 PDO 连接。改为容器单例可显著减少连接数。
瓶颈 3:文件缓存的磁盘 I/O
Cache 类每次 get() 都需要 file_get_contents + unserialize,高并发下磁盘 I/O 可能成为瓶颈。
优化建议:高频缓存切换到 Redis。
瓶颈 4:视图编译缓存无容量管理
Temp/View/ 目录下的编译缓存文件不会自动清理,模板大量修改后可能积累大量过期缓存。
优化建议:定期清理 Temp/View/ 目录,或实现基于访问时间的 LRU 淘汰。
瓶颈 5:全字段模糊查询
whereFullLike() 每次调用执行 SHOW COLUMNS 获取表结构,然后对所有字段做 LIKE '%keyword%' 查询,无法使用索引,大表下性能极差。
优化建议:使用 whereMultiLike() 指定字段,或使用全文索引(FULLTEXT)。
22.3 性能优化建议
- 启用 OPcache:PHP OPcache 可显著提升性能,确保
opcache.enable=1、opcache.memory_consumption=128 - 使用 Redis:将 Token 存储和高频缓存切换到 Redis
- 数据库连接池:高并发场景考虑使用持久连接(
PDO::ATTR_PERSISTENT)或连接池 - 静态资源由 Nginx 处理:图片、CSS、JS 等静态资源直接由 Nginx 提供,不经过 PHP
- 日志采样:高流量下成功日志可采样记录,减少磁盘写入
- Gzip 压缩:Nginx 启用
gzip on,减少响应传输体积
22.4 压力测试参考(预估)
在 PHP 8.1 + OPcache + Nginx 环境下,简单的 echo 'Hello' 控制器预计可达到:
- 单进程:~2000-3000 QPS
- 4 进程 FPM:~8000-12000 QPS
带数据库查询的接口性能取决于数据库响应时间,通常在 100-500 QPS 范围。
第二十三章 扩展开发指南
23.1 添加自定义配置
- 在
Config/目录创建MyConfig.php:
<?php return [ "key1" => "value1", "key2" => ["nested" => "value"], ];
- 在代码中读取:
$config = \XiaoPHP\System\Config\Conf::get('MyConfig');
23.2 添加核心服务到容器
编辑 XiaoPHP/System/Tools/Config/ServiceProvider.php,在 register() 方法中添加:
// 注册单例 $container->singleton(MyService::class, function () { return new MyService(); }); // 添加别名 $container->bind('myservice', function ($c) { return $c->make(MyService::class); });
23.3 创建自定义中间件
当前框架的中间件是硬编码在 Routing.php 中的 Middleware::check()。如需添加自定义中间件逻辑,有两种方式:
方式一:扩展 Middleware 类
<?php // App/Index/Function/MyMiddleware.php class MyMiddleware extends \XiaoPHP\System\Middleware { public function check(): void { // 自定义逻辑 parent::check(); } }
方式二:控制器基类中实现
在控制器基类的构造函数中执行中间件逻辑(详见 7.3 控制器基类)。
23.4 添加自定义验证规则
<?php // App/Index/Function/MyValidate.php class MyValidate extends \XiaoPHP\System\Validate { public static function mobile($data) { // 自定义验证逻辑 return preg_match('/^1[3-9]\d{9}$/', $data) ? $data : false; } }
23.5 添加 CLI 命令
详见 第二十章 CLI 命令行。
23.6 扩展自动加载命名空间
框架的自动加载器已支持 App\ 命名空间映射到 App/ 目录。如需添加新的命名空间映射,编辑 XiaoPHP/console.php Phase 1 的自动加载器:
// 在 spl_autoload_register 回调中添加 $prefix = 'MyNamespace\\'; if (strpos($class, $prefix) === 0) { $shortName = substr($class, strlen($prefix)); $file = __DIR__ . '/../MyNamespace/' . str_replace('\\', '/', $shortName) . '.php'; if (file_exists($file)) { require_once $file; return; } }
或使用 Composer 的 autoload 配置(推荐):
{
"autoload": {
"psr-4": {
"MyNamespace\\": "MyNamespace/"
}
}
}
23.7 自定义错误页
在 XiaoPHP/System/Error/ 目录下创建 {code}.html 文件,模板中可使用 $code 和 $info 变量。
第二十四章 最佳实践
24.1 项目结构最佳实践
App/
├── Admin/ # 后台管理应用
│ ├── Controller/
│ │ ├── AdminBase.php # 后台控制器基类(权限检查)
│ │ ├── Auth.php # 登录/登出
│ │ └── Dashboard.php # 仪表盘
│ ├── Model/
│ │ └── AdminUserModel.php
│ └── View/
│ ├── layout.html # 后台布局
│ └── dashboard/
├── Api/ # API 应用
│ ├── Controller/
│ │ ├── ApiBase.php # API 基类(JSON 响应、Token 可选)
│ │ └── User.php
│ └── Model/
└── Index/ # 前台应用
├── Controller/
└── View/
24.2 控制器最佳实践
- 瘦控制器,胖模型:业务逻辑放在 Model 或 Function 类中,控制器只负责接收请求、调用模型、返回响应
- 使用基类:公共逻辑(权限检查、JSON 响应格式)放在基类
- 验证输入:使用
Validate类验证用户输入 - 避免直接操作超全局变量:可封装 Request 类(框架未提供,可自行扩展)
24.3 数据库最佳实践
- 使用预处理:框架已默认使用,不要自行拼接 SQL
- 必须带 WHERE:UPDATE/DELETE 操作必须带 WHERE 条件
- 使用事务:多表写入操作使用事务保证一致性
- **避免 SELECT ***:查询构造器默认
SELECT *,如需指定字段可使用原生 SQL($db->pdo->query()) - 分页查询:大列表必须分页,避免一次性加载大量数据
- 索引优化:WHERE、ORDER BY、JOIN 的字段建立索引
24.4 视图最佳实践
- 默认自动转义:使用
{{ $var }}输出变量,不要随意使用{{! $var }} - 布局继承:公共部分放在布局模板中,使用
@extends+@section - 模板包含:复用的组件(导航、页脚)使用
@include - 避免复杂逻辑:模板中只做简单的条件和循环,复杂逻辑放在控制器
- 清理编译缓存:模板修改后如未生效,删除
Temp/View/目录
24.5 安全最佳实践
详见 第二十一章 安全审计 的安全最佳实践清单。
24.6 部署最佳实践
- Web 根目录:Nginx
root必须指向Public/,框架其他目录不可对外访问 - 文件权限:
Public/设为 755,Temp/、logs/、runtime/、Resources/PHPFile/设为 Web 服务器用户可写 - 环境隔离:开发、测试、生产使用不同的
.env文件 - 版本控制:
.env、Temp/、logs/、runtime/加入.gitignore - HTTPS:生产环境强制 HTTPS,Session Cookie 设置
secure - 备份:定期备份数据库和上传文件
第二十五章 常见问题 FAQ
Q1:访问页面显示 404?
A:检查以下几点:
- Nginx 伪静态是否配置(
try_files $uri $uri/ /index.php?$args;) - 控制器文件是否存在于
App/应用名/Controller/目录 - 控制器类名是否与文件名一致
- 方法是否为 public
Q2:数据库连接失败?
A:
- 检查
.env中的数据库配置是否正确 - 确认 MySQL 服务是否启动
- 确认数据库用户权限(是否允许从当前主机连接)
- 检查 PHP 是否安装了
pdo_mysql扩展(php -m | grep pdo_mysql)
Q3:视图修改后不生效?
A:删除 Temp/View/ 目录下的编译缓存文件,框架会在下次请求时重新编译。
rm -rf Temp/View/*
Q4:如何关闭某个应用?
A:编辑该应用目录下的 app.json,将 status 改为 "off":
{
"name": "OldApp",
"status": "off"
}
Q5:Token 认证如何跳过某些路由?
A:在 Whitelist/Whitelist.php 中添加白名单:
Whitelist::add("/Api/Public/Login"); Whitelist::add("/Api/Public/*"); // 通配符
Q6:如何在控制器中获取数据库实例?
A:
// 方式一:构造函数注入(推荐) class UserController { private $db; public function __construct(\XiaoPHP\System\Tools\App\MysqlTools $db) { $this->db = $db; } } // 方式二:容器获取 $db = \XiaoPHP\System\Container::getInstance()->make('db'); // 方式三:直接 new(不推荐,每次新建连接) $db = new \XiaoPHP\System\Tools\App\MysqlTools();
Q7:如何返回 JSON 响应?
A:
// 方式一:手动设置 header(控制器方法返回值会被 echo) header('Content-Type: application/json'); return json_encode(['code' => 0, 'msg' => 'success']); // 方式二:使用 Json::encode()(自动 exit) \XiaoPHP\System\Json::encode(['code' => 0, 'msg' => 'success']);
Q8:上传文件大小限制如何修改?
A:框架默认限制 2MB,需修改两处:
XiaoPHP/System/Tools/Function/File.php中的$maxSize属性php.ini中的upload_max_filesize和post_max_size
Q9:如何使用 Redis 存储 Token?
A:编辑 Config/Middleware.php:
return [ "storage" => 'redis', // 改为 redis // ... ];
确保 .env 中 Redis 配置正确且 phpredis 扩展已安装。
Q10:调试模式如何开启?
A:编辑 .env:
DEBUG=true
开启后异常会显示详细的调试信息页。生产环境务必关闭。
Q11:如何创建新应用?
A:
php App/Loading.php add
按提示输入应用名称,自动创建目录结构和 app.json。
Q12:Session 无法保存?
A:框架入口会自动检测 Session 存储目录是否可写,不可写时回退到 runtime/sessions/ 目录。确保该目录存在且 Web 服务器用户有写入权限。
第二十六章 版本历史与升级
26.1 版本信息
- 当前版本:V2.1.3
- 入口文件标注版本:V2.1.2(Public/index.php 注释)
- 应用创建命令标注版本:V2.1.0(App/Loading.php 输出)
- 欢迎页版本:V2.1.3(App/Index/Controller/Index.php)
26.2 已知版本差异
| 文件 | 标注版本 |
|---|---|
Public/index.php |
V2.1.2 |
App/Loading.php |
V2.1.0 |
App/Index/Controller/Index.php |
V2.1.3 |
View.php 注入 meta |
XiaoPHP V2.1.3 |
26.3 升级建议
从旧版本升级到 V2.1.3 时,注意以下变更:
console.php重构为 8 阶段启动加载器,替换了旧的零散 require 方式View.php模板引擎大幅优化,新增 CSS/JS 语法保护、HTML 压缩优化MysqlTools.php新增whereMultiLike()、whereFullLike()、getMinMaxId()、sum()方法Middleware.php支持 Redis 存储和多认证模式File.php新增文件上传安全扫描Auth.php新增 CSRF 双 Token 机制Container.php新增自动装配和循环依赖检测
26.4 升级步骤
- 备份现有项目(代码 + 数据库)
- 替换
XiaoPHP/目录为新版本 - 对比
Config/目录,合并新增配置项 - 检查
Public/index.php是否需要更新 - 删除
Temp/View/编译缓存 - 测试所有功能
附录 A 全局函数与常量速查
A.1 全局常量
| 常量 | 定义位置 | 值 |
|---|---|---|
SYS_PATH |
Public/index.php, XiaoCTL |
框架核心目录绝对路径(.../XiaoPHP) |
SYS_ROOT |
Public/index.php, XiaoCTL |
Web 根目录绝对路径(.../Public) |
CTL |
XiaoCTL |
CLI 模式标识(true) |
A.2 全局函数
| 函数 | 定义位置 | 说明 |
|---|---|---|
Error($code, $info) |
XiaoPHP/System/Error/error.php |
输出 HTTP 错误响应并 exit |
displayDebugInfo(Throwable $e) |
XiaoPHP/debug.php |
渲染调试异常页 |
A.3 超全局变量使用
框架不封装 Request/Response 对象,直接使用 PHP 超全局变量:
$_GET/$_POST/$_FILES/$_COOKIE/$_SESSION/$_SERVER
附录 B 配置项完整清单
B.1 .env 环境变量
| 变量名 | 说明 | 默认值 |
|---|---|---|
DB_HOST |
MySQL 主机 | localhost |
DB_PORT |
MySQL 端口 | 3306 |
DB_NAME |
数据库名 | xiaophp |
DB_USER |
数据库用户 | root |
DB_PASSWORD |
数据库密码 | 123456 |
REDIS_HOST |
Redis 主机 | 127.0.0.1 |
REDIS_PORT |
Redis 端口 | 6379 |
REDIS_PASSWORD |
Redis 密码 | (空) |
DEBUG |
调试模式 | false |
B.2 Config/App.php
| 配置项 | 说明 | 可选值 |
|---|---|---|
debug |
调试模式 | true / false |
error |
错误响应格式 | html / json |
B.3 Config/Mysql.php
| 配置项 | 说明 |
|---|---|
host |
数据库主机 |
port |
数据库端口 |
user |
数据库用户名 |
password |
数据库密码 |
dbname |
数据库名 |
B.4 Config/Redis.php
| 配置项 | 说明 |
|---|---|
host |
Redis 主机 |
port |
Redis 端口 |
password |
Redis 密码 |
B.5 Config/Cache.php
| 配置项 | 说明 | 默认值 |
|---|---|---|
dir |
缓存文件目录 | Temp/Cache/ |
expire |
默认过期时间(秒) | 3600 |
B.6 Config/Logs.php
| 配置项 | 说明 | 可选值 |
|---|---|---|
success |
是否记录成功日志 | true / false |
error |
是否记录错误日志 | true / false |
B.7 Config/Middleware.php
| 配置项 | 说明 | 默认值 |
|---|---|---|
storage |
Token 存储方式 | cache / redis |
token_key |
Token 参数名 | token |
auth_mode |
认证方式(逗号分隔) | bearer,post,cookie |
cookie_name |
Cookie 名称 | auth_token |
cookie_expire |
默认过期时间(秒) | 7200 |
B.8 Config/AliyunDns.php
| 配置项 | 说明 |
|---|---|
accessKeyId |
阿里云 AccessKey ID |
accessKeySecret |
阿里云 AccessKey Secret |
附录 C 类与命名空间索引
C.1 核心类(XiaoPHP\System\)
| 类名 | 文件路径 | 说明 |
|---|---|---|
Container |
System/Tools/Function/Container.php |
DI 容器 |
Helper |
System/Tools/Function/Helper.php |
辅助工具 |
Ipaddr |
System/Tools/Function/Ipaddr.php |
IP 获取 |
Model |
System/Tools/Function/Model.php |
模型基类 |
View |
System/Tools/Function/View.php |
模板引擎 |
Cache |
System/Tools/Function/Cache.php |
文件缓存 |
Logs |
System/Tools/Function/Logs.php |
日志 |
Validate |
System/Tools/Function/Validate.php |
验证器 |
Auth |
System/Tools/Function/Auth.php |
Session 认证 |
Json |
System/Tools/Function/Json.php |
JSON 工具 |
File |
System/Tools/Function/File.php |
文件管理 |
Wget |
System/Tools/Function/Wget.php |
HTTP 客户端 |
AesTool |
System/Tools/encrypt/AesTool.php |
AES 加密 |
RSATool |
System/Tools/encrypt/RSATool.php |
RSA 加密 |
Middleware |
Middleware.php |
Token 中间件 |
C.2 配置类(XiaoPHP\System\Config\)
| 类名 | 文件路径 | 说明 |
|---|---|---|
Conf |
System/Tools/Config/Conf.php |
配置读取器 |
Env |
System/Tools/Config/Env.php |
环境变量加载器 |
Route |
System/Tools/Config/Route.php |
路由注册器 |
Whitelist |
System/Tools/Config/Whitelist.php |
白名单管理器 |
ServiceProvider |
System/Tools/Config/ServiceProvider.php |
服务提供者 |
C.3 应用工具类
| 类名 | 命名空间 | 文件路径 | 说明 |
|---|---|---|---|
MysqlTools |
XiaoPHP\System\Tools\App |
System/Tools/App/MysqlTools.php |
PDO 查询构造器 |
RedisTools |
XiaoPHP\System\Tools\App |
System/Tools/App/RedisTools.php |
Redis 封装 |
AliyunDns |
XiaoPHP\app\tools |
System/Tools/App/AliyunDns.php |
阿里云 DNS |
C.4 全局类(无命名空间)
| 类名 | 文件路径 | 说明 |
|---|---|---|
Loading |
App/Loading.php |
应用加载器 |
Command |
App/Loading.php |
CLI 应用创建命令 |
C.5 脚手架类(Controller\Scaffold\)
| 类名 | 文件路径 | 说明 |
|---|---|---|
System |
Scaffold/Apptools.php |
示例脚手架命令 |