Search by

xiaououo / xiaophp

xiaououo

基于 PHP 的轻量级 MVC 框架,适合小型项目快速开发

Package info

github.com/xiaououo/XiaoPHP

Type:project

pkg:composer/xiaououo/xiaophp

Statistics

Installs: 4

Dependents: 0

Suggesters: 0

Stars: 1

Open Issues: 0

V2.1.3 2026-08-26 11:39 UTC

This package is auto-updated.

Last update: 2026-08-27 12:02:57 UTC


README

作者:小新
版本:V2.1.3
许可证:Apache-2.0
文档生成日期:2026-08-26
代码规模:42 个 PHP 文件,约 4388 行代码

目录

第一章 框架概述

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 设计哲学

  1. 约定优于配置:自动路由按 /应用名/控制器/方法 约定分发,无需手动注册每条路由。
  2. 零依赖核心:框架核心不捆绑第三方库,降低部署门槛和供应链风险。
  3. 安全优先:数据库操作全部使用 PDO 预处理;视图默认 HTML 转义;上传文件扫描 PHP 标签;Token 使用 md5 哈希存储。
  4. 渐进式增强:从最简单的 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() 方法:

  1. 扫描 App/ 下所有子目录
  2. 读取每个应用的 app.json,检查 status
  3. 对启用的应用,递归扫描 Config/Function/Model/ 目录
  4. require_once 所有 PHP 文件

设计意图:Model 和 Function 类不使用命名空间,通过预加载使其在控制器中可直接 new UserModel() 使用。

第五章 配置系统

5.1 配置层级

XiaoPHP 的配置分为三层:

.env(环境变量)→ Config/*.php(配置文件)→ Conf::get()(运行时读取)
  1. .env:存放环境相关的敏感配置(数据库密码、Redis 密码、调试开关),不纳入版本控制
  2. Config/*.php:框架配置文件,通过 Env::Load() 读取 .env 中的值,提供默认值
  3. 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() 函数实现了以下安全校验:

  1. realpath() 解析真实路径
  2. 校验真实路径是否在 Resources/ 目录内(防止路径遍历)
  3. 校验是否为文件
  4. 设置正确的 MIME 类型、Content-Length、Accept-Ranges
  5. 以附件方式下载(Content-Disposition: attachment

注意:当前静态资源一律以附件下载方式输出,不支持内联显示。如需图片内联显示,建议直接由 Nginx 处理 Resources/ 目录。

6.5 路由分发函数

dispatchRoute()

精确路由分发,执行流程:

  1. 解析路由字符串为 [方法, 控制器/方法, 命名空间]
  2. 验证 HTTP 请求方法是否匹配,不匹配返回 405
  3. 查找控制器文件(大小写不敏感)
  4. 加载兄弟控制器(Base 类优先)
  5. require 控制器文件
  6. 容器实例化控制器
  7. 查找方法(大小写不敏感)
  8. 执行方法并 echo 返回值
  9. 记录成功日志

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 安全机制

  1. 预处理语句:所有查询值通过 PDO 预处理绑定,防止 SQL 注入
  2. 表名/列名白名单sanitizeTableName()sanitizeColumnName() 只保留 a-zA-Z0-9_
  3. UPDATE/DELETE 必须带 WHERE:防止全表更新/删除
  4. 错误处理: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 注释

以下标签内容受保护不被压缩:pretextareascriptstyle,以及内联 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 提供两套独立的认证机制:

  1. Token 中间件Middleware 类):基于 Token 的无状态认证,适用于 API
  2. Session 认证Auth 类):基于 Session 的有状态认证,适用于后台管理

10.1 Token 中间件

10.1.1 工作原理

中间件在路由分发前执行 Middleware::check(),对所有非白名单路由进行 Token 认证:

  1. 检查当前 URL 是否在白名单中,是则跳过认证
  2. 按配置的认证模式依次尝试获取 Token
  3. 从存储(文件缓存或 Redis)中验证 Token 是否存在
  4. 验证失败返回 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'),
            ]);
    }
}

用户表需包含字段:idusernamepasswordsaltstatusnicknameemailroleavatar

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 安全机制

文件上传实现了多重安全防护:

  1. 文件大小限制:默认最大 2MB($maxSize = 2 * 1024 * 1024
  2. MIME 类型校验:使用 finfo_open(FILEINFO_MIME_TYPE) 检测文件真实 MIME(非扩展名)
  3. PHP 代码扫描:读取文件前 4096 字节,检测是否包含 <?php<?=<script language=php> 等标签,防止上传 Webshell
  4. 随机文件名:默认使用 bin2hex(random_bytes(16)) 生成随机文件名,避免路径遍历和覆盖
  5. 文件名过滤:非随机命名时,文件名只保留 a-zA-Z0-9_\-. 字符
  6. 目录校验:存储目录不存在时自动创建

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() 方法解析路径:

  1. 拼接存储根目录和相对路径
  2. realpath() 解析真实路径
  3. 校验真实路径是否以存储根目录开头(防止路径遍历 ../../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 签名:

  1. 合并公共参数和业务参数
  2. 按参数名字典排序
  3. 构造规范化查询字符串
  4. 构造待签名字符串:GET&%2F&{urlencode(查询字符串)}
  5. 使用 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 注册三个处理器:

  1. register_shutdown_function:捕获致命错误(E_ERROR、E_PARSE、E_CORE_ERROR、E_COMPILE_ERROR),转为 ErrorException 并调用 displayDebugInfo()
  2. set_error_handler:将所有 PHP 错误(warning、notice 等)转为 ErrorException 抛出
  3. set_exception_handler:捕获未处理的异常,调用 displayDebugInfo()

19.3 调试信息页(debug.php)

Config/App.phpdebug = 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.phperror 配置决定:

  • 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"

执行流程:

  1. 解析 类名:方法名
  2. 类名和方法名经过正则过滤(只保留 a-zA-Z0-9_
  3. 拼接为 \Controller\Scaffold\{类名}
  4. 检查类和方法是否存在
  5. 静态调用方法,传入参数
  6. 输出返回值

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.phpserveStatic()

描述:所有静态资源都设置 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 可被直接下载。

建议

  1. 确保 Nginx root 指向 Public/ 目录
  2. 在 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 性能优化建议

  1. 启用 OPcache:PHP OPcache 可显著提升性能,确保 opcache.enable=1opcache.memory_consumption=128
  2. 使用 Redis:将 Token 存储和高频缓存切换到 Redis
  3. 数据库连接池:高并发场景考虑使用持久连接(PDO::ATTR_PERSISTENT)或连接池
  4. 静态资源由 Nginx 处理:图片、CSS、JS 等静态资源直接由 Nginx 提供,不经过 PHP
  5. 日志采样:高流量下成功日志可采样记录,减少磁盘写入
  6. 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 添加自定义配置

  1. Config/ 目录创建 MyConfig.php
<?php
return [
    "key1" => "value1",
    "key2" => ["nested" => "value"],
];
  1. 在代码中读取:
$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 控制器最佳实践

  1. 瘦控制器,胖模型:业务逻辑放在 Model 或 Function 类中,控制器只负责接收请求、调用模型、返回响应
  2. 使用基类:公共逻辑(权限检查、JSON 响应格式)放在基类
  3. 验证输入:使用 Validate 类验证用户输入
  4. 避免直接操作超全局变量:可封装 Request 类(框架未提供,可自行扩展)

24.3 数据库最佳实践

  1. 使用预处理:框架已默认使用,不要自行拼接 SQL
  2. 必须带 WHERE:UPDATE/DELETE 操作必须带 WHERE 条件
  3. 使用事务:多表写入操作使用事务保证一致性
  4. **避免 SELECT ***:查询构造器默认 SELECT *,如需指定字段可使用原生 SQL($db->pdo->query()
  5. 分页查询:大列表必须分页,避免一次性加载大量数据
  6. 索引优化:WHERE、ORDER BY、JOIN 的字段建立索引

24.4 视图最佳实践

  1. 默认自动转义:使用 {{ $var }} 输出变量,不要随意使用 {{! $var }}
  2. 布局继承:公共部分放在布局模板中,使用 @extends + @section
  3. 模板包含:复用的组件(导航、页脚)使用 @include
  4. 避免复杂逻辑:模板中只做简单的条件和循环,复杂逻辑放在控制器
  5. 清理编译缓存:模板修改后如未生效,删除 Temp/View/ 目录

24.5 安全最佳实践

详见 第二十一章 安全审计 的安全最佳实践清单。

24.6 部署最佳实践

  1. Web 根目录:Nginx root 必须指向 Public/,框架其他目录不可对外访问
  2. 文件权限Public/ 设为 755,Temp/logs/runtime/Resources/PHPFile/ 设为 Web 服务器用户可写
  3. 环境隔离:开发、测试、生产使用不同的 .env 文件
  4. 版本控制.envTemp/logs/runtime/ 加入 .gitignore
  5. HTTPS:生产环境强制 HTTPS,Session Cookie 设置 secure
  6. 备份:定期备份数据库和上传文件

第二十五章 常见问题 FAQ

Q1:访问页面显示 404?

A:检查以下几点:

  1. Nginx 伪静态是否配置(try_files $uri $uri/ /index.php?$args;
  2. 控制器文件是否存在于 App/应用名/Controller/ 目录
  3. 控制器类名是否与文件名一致
  4. 方法是否为 public

Q2:数据库连接失败?

A

  1. 检查 .env 中的数据库配置是否正确
  2. 确认 MySQL 服务是否启动
  3. 确认数据库用户权限(是否允许从当前主机连接)
  4. 检查 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,需修改两处:

  1. XiaoPHP/System/Tools/Function/File.php 中的 $maxSize 属性
  2. php.ini 中的 upload_max_filesizepost_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 时,注意以下变更:

  1. console.php 重构为 8 阶段启动加载器,替换了旧的零散 require 方式
  2. View.php 模板引擎大幅优化,新增 CSS/JS 语法保护、HTML 压缩优化
  3. MysqlTools.php 新增 whereMultiLike()whereFullLike()getMinMaxId()sum() 方法
  4. Middleware.php 支持 Redis 存储和多认证模式
  5. File.php 新增文件上传安全扫描
  6. Auth.php 新增 CSRF 双 Token 机制
  7. Container.php 新增自动装配和循环依赖检测

26.4 升级步骤

  1. 备份现有项目(代码 + 数据库)
  2. 替换 XiaoPHP/ 目录为新版本
  3. 对比 Config/ 目录,合并新增配置项
  4. 检查 Public/index.php 是否需要更新
  5. 删除 Temp/View/ 编译缓存
  6. 测试所有功能

附录 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 示例脚手架命令