FleaPHP 是一个轻量级的 PHP MVC 框架,采用 PSR-4 自动加载机制,支持 PHP 7.4+。
| 项目 | 说明 |
|---|---|
| 版本 | 2.3.0 (开发中) |
| 命名空间 | FLEA\ |
| PHP 要求 | 7.4+ |
| 许可证 | MIT |
| 组件 | PSR 标准 | 说明 |
|---|---|---|
FLEA\Container |
PSR-11 | 依赖注入容器 |
FLEA\Cache |
PSR-16 | 缓存接口 |
FLEA\Log |
PSR-3 | 日志接口 |
src/
├── FLEA.php # 框架入口文件(全局类)
├── Functions.php # 全局函数(flea_context 等)
└── FLEA/ # 框架核心代码(命名空间 FLEA\Xxx 的根)
├── Auth/ # 认证支持
│ ├── Jwt.php # JWT 工具 (HS256)
│ └── JwtException.php # JWT 异常
├── Cache/ # 缓存驱动
│ ├── FileCache.php # 文件缓存 (PSR-16)
│ └── RedisCache.php # Redis 缓存 (PSR-16)
├── Config/ # 配置相关
│ └── Defaults.php # 默认配置
├── Context/ # 上下文管理(请求级状态)
│ ├── Context.php # 核心类
│ ├── DriverInterface.php # 驱动接口
│ ├── IdentityInterface.php # 身份标识接口
│ ├── TraceContext.php # 链路追踪上下文
│ ├── Driver/ # 存储驱动
│ │ ├── SessionDriver.php # Session 存储
│ │ ├── RedisDriver.php # Redis 存储
│ │ ├── FileDriver.php # 文件存储
│ │ └── DatabaseSessionDriver.php # 数据库存储
│ └── Identity/ # 身份标识
│ ├── SessionIdentity.php # Session ID
│ ├── JwtIdentity.php # JWT 用户
│ ├── ApiKeyIdentity.php # API Key
│ └── RequestIdIdentity.php # Request ID
├── Controller/ # 控制器基类
│ └── Action.php # 动作控制器基类
├── Db/ # 数据库相关组件
│ ├── Driver/ # 数据库驱动
│ │ ├── AbstractDriver.php # 抽象基类
│ │ └── Mysql.php # MySQL 驱动
│ ├── Exception/ # 数据库异常
│ ├── TableLink/ # 表关联处理
│ │ ├── HasOneLink.php
│ │ ├── BelongsToLink.php
│ │ ├── HasManyLink.php
│ │ └── ManyToManyLink.php
│ ├── SqlHelper.php # SQL 辅助
│ ├── SqlStatement.php # SQL 语句处理
│ ├── TableDataGateway.php # 表数据入口 (CRUD)
│ └── TableLink.php # 表关联基类
├── Dispatcher/ # 请求调度器
│ ├── Exception/
│ │ └── CheckFailed.php
│ ├── Auth.php # 认证调度器
│ └── Simple.php # 简单调度器
├── Error/ # 错误处理
│ ├── ErrorRenderer.php # 错误渲染器
│ └── views/
│ └── 500.php
├── Exception/ # 框架通用异常
├── Helper/ # 辅助类
│ ├── FileUploader/
│ │ └── File.php
│ ├── FileUploader.php # 文件上传
│ ├── HttpClient.php # HTTP 客户端(服务间调用)
│ ├── Image.php # 图像处理
│ ├── ImgCode.php # 验证码(v2.1.0 重构)
│ ├── Pager.php # 分页器
│ ├── Str.php # 字符串工具(命名参数提取)
│ └── Verifier.php # 数据验证
├── Middleware/ # 中间件
│ ├── MiddlewareInterface.php # 中间件接口
│ ├── Pipeline.php # 中间件管道
│ ├── CorsMiddleware.php # CORS 中间件
│ ├── AuthMiddleware.php # 认证中间件
│ └── RateLimitMiddleware.php # 限流中间件
├── Rbac/ # RBAC 子组件
│ ├── Exception/
│ │ ├── InvalidACT.php
│ │ └── InvalidACTFile.php
│ ├── RolesManager.php # 角色管理
│ └── UsersManager.php # 用户管理
├── Acl/ # ACL 子组件
│ ├── Exception/
│ │ └── UserGroupNotFound.php
│ ├── Table/ # ACL 数据表
│ ├── Manager.php # ACL 管理器
│ ├── testACL.php
│ └── testCreateData.php
├── View/ # 视图引擎(v2.1.0 重构)
│ ├── ViewInterface.php # 视图顶层接口
│ ├── StreamingViewInterface.php # 流式视图接口(SSE 等)
│ ├── FileTemplateView.php # 文件模板视图(HTML/XML/Markdown 等)
│ ├── JsonView.php # JSON 数据视图
│ ├── CsvView.php # CSV 导出视图(支持 Excel 兼容模式)
│ ├── RedirectView.php # 重定向视图
│ ├── BinaryView.php # 二进制文件视图(PDF/Excel/图片下载)
│ ├── SseView.php # SSE 流式视图
│ ├── CallbackView.php # 回调视图(特殊场景扩展)
│ ├── CallbackViewBuilder.php # 回调视图构建器(链式 API)
│ ├── RendererConfig.php # 渲染器配置类
│ ├── SimpleRenderer.php # 简单 PHP 模板渲染器(静态类)
│ └── NullView.php # 空视图(空对象模式)
├── Cache.php # 缓存门面 (PSR-16)
│── Config.php # 配置管理器 (单例)
│── Container.php # 对象容器 (PSR-11)
│── Database.php # 数据库连接管理
│── Env.php # 环境检测工具
│── Exception.php # 基础异常类
│── Language.php # 多语言支持
│── Log.php # 日志服务 (PSR-3)
│── Request.php # HTTP 请求封装
│── Response.php # HTTP 响应封装
│── Router.php # HTTP 路由器
│── Route.php # 单条路由
│── Rbac.php # RBAC 服务类
demo/
├── .env # 环境变量(基础配置)
├── .env.local # 本地开发配置(可选)
├── .env.production # 生产环境配置(可选)
├── App/
│ ├── Config.php # 应用配置
│ ├── Controller/ # 应用控制器
│ ├── Model/ # 应用模型
│ └── View/ # 应用视图
└── public/
└── index.php # Web 入口
框架主入口类,所有方法为静态方法,委托给各服务类:
class FLEA
{
// 配置管理
public static function loadEnv(string $path): void
public static function loadAppInf($config = null): void
public static function getAppInf(string $option, $default = null)
public static function setAppInf($option, $data = null): void
public static function setAppInfValue(string $option, string $keyname, $value): void
public static function getAppInfValue(string $option, string $keyname, $default = null)
// 对象容器(PSR-11)
public static function getSingleton(string $className): object
public static function register(object $obj, ?string $name = null): object
public static function isRegistered(string $name): bool
// 数据库
public static function getDBO($dsn = 0): \FLEA\Db\Driver\AbstractDriver
public static function parseDSN($dsn): ?array
// 缓存
public static function getCache(string $cacheId, int $time = 900, ...): mixed
public static function writeCache(string $cacheId, $data): bool
public static function purgeCache(string $cacheId): bool
// 中间件
public static function middleware(\FLEA\Middleware\MiddlewareInterface $mw): void
// 应用启动
public static function runMVC(): void
public static function init(bool $loadMVC = false): void
}在 src/Functions.php 中定义的全局函数:
// 环境变量
env(string $key, $default = null): mixed
// 日志
log_message($msg, $level = 'debug', $title = ''): void
// SQL 语句
sql_statement($sql): \FLEA\Db\SqlStatement
// 路由
url(string $name, array $params = []): string
// 字符串转换
kebab_to_pascal(string $value): string
// order-apply → OrderApply
// user-list → UserList
// HTML 转义
h(string $text): string
t(string $text): string
// Context 上下文
flea_context(): \FLEA\Context\Context
// 链路追踪
generate_traceid(): string
TraceContext::getTraceId(): string
TraceContext::getFullTraceId(): string
TraceContext::childSpan(): string
// 翻译
_T(string $key, string $language = ''): string
load_language(string $dictname, string $language = ''): bool
// 调试
dump($vars, string $label = '', bool $return = false): ?string
dump_trace(): void
print_ex(\Throwable $ex, bool $return = false): string
// 文件操作
safe_file_put_contents(string $filename, string $content): bool
safe_file_get_contents(string $filename): ?string
mkdirs(string $dir, int $mode = 0777): bool
rmdirs(string $dir): bool
// 数组操作
array_remove_empty(array &$arr, bool $trim = true): void
array_col_values(array $arr, string $col): array
array_to_hashmap(array &$arr, string $keyField, ?string $valueField = null): array
array_group_by(array &$arr, string $keyField): array
array_to_tree(array $arr, string $fid, string $parentIdKey = 'parent_id', string $childrenIdKey = 'children', bool $returnReferences = false): array
tree_to_array(array &$node, string $fchildren = 'children'): array
array_column_sort(array $array, string $key, int $sort = SORT_ASC): array
array_sortby_multifields(array $rowset, array $args): array单例模式,管理应用程序配置:
namespace FLEA;
class Config
{
public array $appInf = [];
public static function getInstance(): self
public function getAppInf(string $option, $default = null)
public function setAppInf($option, $data = null): void
public function getAppInfValue(string $option, string $keyname, $default = null)
public function setAppInfValue(string $option, string $keyname, $value): void
public function mergeAppInf(array $config): void
}实现 PSR-11 依赖注入容器:
namespace FLEA;
class Container implements \Psr\Container\ContainerInterface
{
public function get(string $id): mixed // PSR-11: 获取对象
public function has(string $id): bool // PSR-11: 检查是否存在
public function register(object $obj, ?string $name = null): object
public function singleton(string $className): object
public function all(): array
}管理数据库连接池:
namespace FLEA;
class Database
{
public static function getInstance(): self
public function connect($dsn = 0): \FLEA\Db\Driver\AbstractDriver
public function parseDSN($dsn): ?array
}PSR-16 缓存门面:
namespace FLEA;
class Cache
{
public static function provider(): \Psr\SimpleCache\CacheInterface
}配置项 cacheProvider:
null(默认) →FLEA\Cache\FileCache\FLEA\Cache\RedisCache::class→ Redis
实现 PSR-3 LoggerInterface:
namespace FLEA;
class Log extends \Psr\Log\AbstractLogger
{
public string $traceId;
public bool $enabled = true;
public ?string $logFileDir;
public ?string $logFilename;
public function log($level, $message, array $context = []): void // PSR-3
public function flush(): void
public function getTraceId(): string
}namespace FLEA;
class Env
{
public static function isEnv(string $env): bool
public static function isLocal(): bool
public static function isProduction(): bool
public static function isDevelopment(): bool
}Context 提供请求级别的状态管理服务,支持多种存储驱动和身份标识,用于替代传统的 $_SESSION。
namespace FLEA\Context;
class Context
{
public function get(string $key, mixed $default = null): mixed
public function set(string $key, mixed $value, ?int $ttl = null): bool
public function remove(string $key): bool
public function has(string $key): bool
}namespace FLEA\Context;
interface DriverInterface
{
public function get(string $key, mixed $default = null): mixed;
public function set(string $key, mixed $value, ?int $ttl = null): bool;
public function remove(string $key): bool;
public function has(string $key): bool;
}namespace FLEA\Context;
interface IdentityInterface
{
public function getId(): string;
}| 驱动 | 类 | 说明 |
|---|---|---|
| SessionDriver | FLEA\Context\Driver\SessionDriver |
使用 $_SESSION 存储 |
| RedisDriver | FLEA\Context\Driver\RedisDriver |
使用 Redis 存储 |
| FileDriver | FLEA\Context\Driver\FileDriver |
使用文件系统存储 |
| DatabaseSessionDriver | FLEA\Context\Driver\DatabaseSessionDriver |
使用数据库存储 |
| 身份标识 | 类 | 说明 |
|---|---|---|
| SessionIdentity | FLEA\Context\Identity\SessionIdentity |
使用 session_id() |
| JwtIdentity | FLEA\Context\Identity\JwtIdentity |
从 JWT 提取用户 ID |
| ApiKeyIdentity | FLEA\Context\Identity\ApiKeyIdentity |
使用 API Key 哈希 |
| RequestIdIdentity | FLEA\Context\Identity\RequestIdIdentity |
使用 X-Request-ID |
return [
// Context 驱动:session/redis/file/database
'contextDriver' => env('CONTEXT_DRIVER', 'session'),
// 身份标识:session/jwt/api-key/request-id
'contextIdentity' => env('CONTEXT_IDENTITY', 'session'),
// 各驱动的详细配置
'context' => [
'redis' => [
'host' => env('CONTEXT_REDIS_HOST', '127.0.0.1'),
'port' => (int) env('CONTEXT_REDIS_PORT', 6379),
'password' => env('CONTEXT_REDIS_PASSWORD', ''),
'prefix' => env('CONTEXT_REDIS_PREFIX', 'fleaphp:context:'),
],
'file' => [
'path' => env('CONTEXT_FILE_PATH', ''),
],
'database' => [
'tableName' => env('CONTEXT_DB_TABLE', 'contexts'),
'fieldId' => env('CONTEXT_DB_FIELD_ID', 'context_id'),
'fieldData' => env('CONTEXT_DB_FIELD_DATA', 'context_data'),
'fieldActivity' => env('CONTEXT_DB_FIELD_ACTIVITY', 'activity'),
'lifeTime' => (int) env('CONTEXT_DB_LIFETIME', 3600),
],
],
];// 获取 Context 实例
$context = flea_context();
// 读写数据
flea_context()->set('user_id', 123);
$user_id = flea_context()->get('user_id');TraceContext 提供分布式链路追踪的 TraceID 和 SpanID 管理,支持接收外部传入的 trace_id,形成完整的调用链。
{trace_id}-{span_id}
示例:abc123-1.2.1
trace_id: 全局唯一追踪 ID(62 进制 5 位随机字符串)span_id: Span 层级标识(如0、0.1、0.1.1)
namespace FLEA\Context;
class TraceContext
{
// 初始化 TraceID(框架自动调用)
public static function init(): void
// 获取 TraceID
public static function getTraceId(): string
// 获取 SpanID
public static function getSpanId(): string
// 获取完整的 TraceID(含 SpanID)
public static function getFullTraceId(): string
// 生成子 SpanID(用于下游调用)
public static function childSpan(): string
// 从 Context 获取 TraceID(便捷方法)
public static function fromContext(): string
}| 请求头 | 说明 | 格式示例 |
|---|---|---|
X-Trace-Id |
FLEA 框架标准 | abc123-0.1 |
Traceparent |
W3C 标准 | 00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01 |
// 框架自动初始化,无需手动调用
// 获取 TraceID(用于日志记录)
$traceId = TraceContext::getTraceId();
// 获取完整 TraceID(用于响应头)
$fullId = TraceContext::getFullTraceId();
header('X-Trace-Id: ' . $fullId);
// 发起下游调用时生成子 SpanID
$childSpan = TraceContext::childSpan();
$response = http_post($url, [], ['X-Trace-Id: ' . $childSpan]);return [
// 日志配置中启用 TraceID
'logEnabled' => env('LOG_ENABLED', false),
'logFileDir' => env('LOG_FILE_DIR', 'cache'),
'logFilename' => env('LOG_FILENAME', 'app.log'),
];日志自动集成:启用日志后,每条日志会自动附加 TraceID。
框架自动在响应中添加 X-Trace-Id 头:
X-Trace-Id: abc123-1.2.1
namespace FLEA;
class Request
{
public static function current(): self
// 请求方法
public function method(): string
public function isGet(): bool
public function isPost(): bool
public function isPut(): bool
public function isDelete(): bool
public function isAjax(): bool
public function isJson(): bool
// 数据获取
public function input(string $key, $default = null): mixed
public function json(string $key = null, $default = null): mixed
public function get(string $key, $default = null): mixed
public function post(string $key, $default = null): mixed
public function param(string $key, $default = null): mixed
public function all(): array
// 请求头/认证
public function header(string $name, $default = null): ?string
public function bearerToken(): ?string
public function ip(): string
public function uri(): string
}namespace FLEA;
class Response // 门面(Facade),单例
{
// 获取当前实例
public static function current(): self
// 工厂方法
public static function fromView(\FLEA\View\ViewInterface $view): self
public static function success($data = null, string $message = 'ok', int $httpCode = 200): self
public static function error(string $message, int $httpCode = 400, int $errCode = -1): self
public static function paginate(array $items, int $total, int $page, int $pageSize): self
// 链式设置
public function withHeader(string $name, string $value): self
public function withStatus(int $statusCode): self
public function setView(\FLEA\View\ViewInterface $view): self
// 获取器
public function getView(): ?\FLEA\View\ViewInterface
public function getStatusCode(): int
public function getHeaders(): array
public function hasContent(): bool
// 发送响应(受 Signal 控制,委托给 HttpResponse)
public function send(): void
}HttpResponse(适配器):
namespace FLEA;
class HttpResponse // 数据容器 + 实际发送
{
public function allowSend(): void
public function setView(\FLEA\View\ViewInterface $view): self
public function withHeader(string $name, string $value): self
public function withStatus(int $statusCode): self
public function getView(): ?\FLEA\View\ViewInterface
public function getStatusCode(): int
public function getHeaders(): array
public function send(): void
}门面模式:
Response是单例门面,通过Response::current()获取- 内部持有
HttpResponse实例,所有操作委托给它 Response订阅 Signal 信号,收到后通知HttpResponse允许发送HttpResponse不依赖 Signal,只接收allowSend()指令
Signal 信号机制:
Response构造时订阅response.send信号- 响应内容设置在任何地方都可以进行(action、中间件)
- 只有
FLEA::runMVC()在所有中间件执行完毕后发布response.send信号 - 中间件中调用
send()会抛出RuntimeException
View 响应处理:
setView()/fromView()绑定 View 到 HttpResponsesend()根据 View 类型自动处理:StreamingViewInterface:调用stream()流式输出RedirectView:发送 Location 头CsvView/BinaryView:添加 Content-Disposition 头JsonView:设置状态码- 其他:输出
getContent()内容
统一响应结构:
// 成功
{"code": 0, "message": "ok", "data": {...}}
// 错误
{"code": -1, "message": "error message", "data": null}namespace FLEA;
class Router
{
// 路由注册
public static function get(string $path, string $handler, array $middlewares = []): \FLEA\Route
public static function post(string $path, string $handler, array $middlewares = []): \FLEA\Route
public static function put(string $path, string $handler, array $middlewares = []): \FLEA\Route
public static function patch(string $path, string $handler, array $middlewares = []): \FLEA\Route
public static function delete(string $path, string $handler, array $middlewares = []): \FLEA\Route
public static function any(string $path, string $handler, array $middlewares = []): \FLEA\Route
public static function group(string $prefix, callable $callback, array $middlewares = []): void
// RESTful 资源路由
public static function resource(string $name, string $controller, array $options = []): void
// 命名路由
public static function urlFor(string $name, array $params = []): string
// 路由匹配
public static function dispatch(): bool
public static function getMatchedMiddlewares(): array
public static function routes(): array
}路由语法:
Router::get('/users', 'UserController@index');
Router::get('/users/{id:\d+}', 'UserController@show');
Router::post('/users', 'UserController@store', [new AuthMiddleware()]);
Router::group('/admin', fn() => {
Router::get('/stats', 'AdminController@stats');
}, [new AuthMiddleware()]);RESTful 资源路由:
// 生成全部 7 条路由
Router::resource('post', 'PostController');
// 只保留部分方法
Router::resource('post', 'PostController', ['only' => ['index', 'show']]);
// 排除部分方法
Router::resource('post', 'PostController', ['except' => ['create', 'edit']]);生成的路由表:
| 方法 | URI | 处理器 | 路由名 |
|---|---|---|---|
| GET | /{name} | {controller}@index | {name}.index |
| GET | /{name}/create | {controller}@create | {name}.create |
| POST | /{name} | {controller}@store | {name}.store |
| GET | /{name}/{id} | {controller}@show | {name}.show |
| GET | /{name}/{id}/edit | {controller}@edit | {name}.edit |
| PUT | /{name}/{id} | {controller}@update | {name}.update |
| PUT | /{name}/{id} | {controller}@update | {name}.update.post (fallback) |
| DELETE | /{name}/{id} | {controller}@destroy | {name}.destroy |
| POST | /{name}/{id} | {controller}@destroy | {name}.destroy.post (fallback) |
说明:
resource()方法一行代码生成 7 条 RESTful 路由- 支持
only(白名单)和except(黑名单)选项过滤路由 - update 和 destroy 额外注册 POST fallback 路由,兼容 HTML 表单只支持 GET/POST 的限制
- 路由名格式:
{name}.{action}(如post.index、post.update.post)
namespace FLEA;
class Route
{
public function name(string $name): self // 命名路由,支持链式调用
}namespace FLEA\Middleware;
interface MiddlewareInterface
{
/**
* @param callable $next 下一个中间件或请求处理器
* @return void
*/
public function handle(callable $next): void;
}管道实现:
namespace FLEA\Middleware;
class Pipeline
{
public static function create(): self
public function pipe(MiddlewareInterface $middleware): self
public function run(callable $destination): void // void 返回
}已实现中间件:
CorsMiddleware- CORS 跨域支持AuthMiddleware- JWT 认证RateLimitMiddleware- 请求限流
namespace FLEA\Auth;
class Jwt
{
public static function encode(array $payload, ?int $ttl = null): string
public static function decode(string $token): array
public static function verify(string $token): bool
}配置项:
jwtSecret: 签名密钥(必须)jwtTtl: 有效期(秒),默认 7200jwtIssuer: 签发者(可选)
namespace FLEA\Controller;
class Action
{
protected string $controllerName;
protected string $actionName;
protected ?\FLEA\Dispatcher\Simple $dispatcher;
public $components = [];
// 生命周期
public function setController(string $controllerName, string $actionName): void
public function setDispatcher(\FLEA\Dispatcher\Simple $dispatcher): void
public function beforeExecute($actionMethod): void
public function afterExecute($actionMethod): void
// 辅助方法
protected function getComponent(string $componentName): object
protected function getDispatcher(): ?\FLEA\Dispatcher\Simple
protected function url(?string $actionName = null, ?array $args = null, ?string $anchor = null): string
protected function forward(?string $controllerName = null, ?string $actionName = null): void
protected function getView(): \FLEA\View\ViewInterface
protected function executeView(string $viewName, ?array $data = null): void
protected function isPost(): bool
protected function isAjax(): bool
}新架构设计思想:View 负责内容生成,Response 负责 HTTP 响应细节
顶层接口:
namespace FLEA\View;
interface ViewInterface
{
public function getContentType(): string;
public function getContent(): string;
}
interface StreamingViewInterface extends ViewInterface
{
public function stream(): void;
}具体视图实现:
namespace FLEA\View;
// 文件模板视图(HTML/XML/Markdown 等)
class FileTemplateView implements ViewInterface
{
public function __construct(?string $template = null, array $vars = [], string $contentType = 'text/html', ?RendererConfig $config = null)
public function setTemplate(string $template): self
public function assign($key, $value = null): self
public function setRendererConfig(RendererConfig $config): self
public function getContentType(): string
public function getContent(): string
}
// JSON 数据视图
class JsonView implements ViewInterface
{
public function __construct($data, int $statusCode = 200)
public function getContentType(): string
public function getContent(): string
public function getStatusCode(): int
}
// CSV 导出视图
class CsvView implements ViewInterface
{
public function __construct(array $rows, string $delimiter = ',', string $filename = 'export.csv', bool $excelCompatible = false)
public function getContentType(): string
public function getContent(): string
public function getFilename(): string
}
// 重定向视图
class RedirectView implements ViewInterface
{
public function __construct(string $url, int $statusCode = 302)
public function getContentType(): string
public function getContent(): string
public function getUrl(): string
public function getStatusCode(): int
}
// 二进制文件视图(支持流式输出)
class BinaryView implements ViewInterface
{
public function __construct(string $filePath, string $filename, string $mimeType)
public function getContentType(): string
public function getContent(): string|resource
public function getFilename(): string
}
// SSE 流式视图
class SseView implements StreamingViewInterface
{
public function __construct(callable $generator)
public function getContentType(): string
public function getContent(): string
public function stream(): void
}
// 回调视图(特殊场景扩展)
class CallbackView implements ViewInterface
{
public function __construct($data, string $contentType, callable $callback)
public function getContentType(): string
public function getContent(): string
}
// 回调视图构建器(链式 API)
class CallbackViewBuilder
{
public function type(string $contentType): self
public function handler(callable $callback): self
public function toView($data): CallbackView
}
// 空视图(空对象模式)
class NullView implements ViewInterface
{
public function __construct(string $contentType = 'text/html')
public function getContentType(): string
public function getContent(): string
}渲染器配置:
namespace FLEA\View;
class RendererConfig
{
public ?string $templateDir = null;
public string $cacheDir = './cache';
public int $cacheLifetime = 900;
public bool $enableCache = true;
public function __construct(array $config = [])
}
class SimpleRenderer
{
public static function configure(RendererConfig $config): void
public static function render(string $template, array $vars = [], ?RendererConfig $config = null): string
}视图工厂类:
namespace FLEA;
class View
{
public static function render(string $template, array $vars = [], string $contentType = 'text/html'): FileTemplateView
public static function html(string $template, array $vars = []): FileTemplateView
public static function xml(string $template, array $vars = []): FileTemplateView
public static function json($data, int $status = 200): JsonView
public static function csv(array $rows, string $filename = 'export.csv', string $delimiter = ',', bool $excelCompatible = false): CsvView
public static function redirect(string $url, int $code = 302): RedirectView
public static function binary(string $filePath, string $filename, string $mimeType): BinaryView
public static function sse(callable $generator): SseView
public static function callback($data, string $contentType, callable $callback): CallbackView
public static function build(): CallbackViewBuilder
public static function pdf(string $filePath, string $filename = 'document.pdf'): BinaryView
public static function excel(string $filePath, string $filename = 'data.xlsx'): BinaryView
public static function image(string $filePath, string $filename = 'image.jpg', string $mimeType = 'image/jpeg'): BinaryView
}迁移指南(旧代码):
旧版 Simple 视图已删除,旧代码需要改写:
// 旧代码(已废弃)
$view = new \FLEA\View\Simple();
$view->assign('posts', $posts);
$view->display('post/index.php');
// 新代码(推荐)
return View::html('post/index.php', ['posts' => $posts]);
// 或直接实例化
return new FileTemplateView('post/index.php', ['posts' => $posts]);namespace FLEA\Dispatcher;
class Simple
{
public function __construct(array &$request)
public function dispatching()
public function getControllerName(): string
public function getActionName(): string
public function setControllerName(string $controllerName): void
public function setActionName(string $actionName): void
public function getControllerClass(string $controllerName): string
protected function executeAction(string $controllerName, string $actionName, string $controllerClass)
protected function loadController(string $controllerClass): bool
}namespace FLEA\Db;
class TableDataGateway
{
public string $schema;
public string $tableName;
public string $fullTableName;
public $primaryKey; // string|array|null
public array $hasOne;
public array $belongsTo;
public array $hasMany;
public array $manyToMany;
public array $meta;
public bool $autoValidating;
public ?\FLEA\Helper\Verifier $verifier;
// CRUD
public function find($conditions, $sort = null, $fields = '*', $queryLinks = true): ?array
public function findAll($conditions = null, $sort = null, $limit = null, $fields = '*', $queryLinks = true): array
public function findCount($conditions = null): int
public function create(array &$row, bool $saveLinks = true): int
public function update(array &$row, bool $saveLinks = true): bool
public function remove(array &$row, bool $removeLink = true): bool
public function save(array &$row): bool
public function removeByPkv($id): bool
}关联常量:
| 常量 | 值 | 说明 |
|---|---|---|
HAS_ONE |
1 | 一对一关联 |
BELONGS_TO |
2 | 从属关联 |
HAS_MANY |
3 | 一对多关联 |
MANY_TO_MANY |
4 | 多对多关联 |
namespace FLEA\Db\Driver;
abstract class AbstractDriver
{
public array $dsn;
public ?\PDO $connection;
public function connect(): void
public function disconnect(): void
public function execute(string $sql): int|bool
public function getOne(string $sql): mixed
public function getAll(string $sql): array
public function insert(string $table, array $fields, array $values): int
public function update(string $table, array $fields, array $values, string $where): bool
public function delete(string $table, string $where): bool
public function qstr(string $str): string
public function qfield(string $field): string
public function qtable(string $table): string
public function insertID(): int
public function affectedRows(): int
}SQL 语句封装类,用于统一管理 SQL 语句字符串和 PDOStatement 对象。
namespace FLEA\Db;
class SqlStatement
{
// 构造函数:只接受 string 或 PDOStatement
public function __construct($sql)
public function isResource(): bool // 是否为 PDOStatement 对象
public function getSql(): \PDOStatement|string
public static function create($sql): self // 工厂方法
}异常:
\FLEA\Exception\TypeMismatch- 当传入参数不是 string 或 PDOStatement 时抛出
namespace FLEA;
class Rbac
{
public string $sessionKey;
public string $rolesKey;
public function setUser(array $userData, $rolesData = null): void
public function getUser(): ?array
public function clearUser(): void
public function getRoles(): mixed
public function getRolesArray(): array
public function check(array &$roles, array &$ACT): bool
public function prepareACT(array $ACT): array
}注意:Rbac 内部使用 flea_context() 存储用户数据,支持 Session/JWT 等多种存储方式。
namespace FLEA\Acl;
class Manager
{
public array $tableClass;
public function __construct(array $tableClass = [])
public function getUserWithPermissions($conditions): ?array
}namespace FLEA\Helper;
class Pager
{
public $source;
public ?\FLEA\Db\Driver\AbstractDriver $dbo;
public $conditions;
public ?string $sortby;
public int $basePageIndex;
public int $pageSize;
public int $totalCount;
public int $pageCount;
public int $currentPage;
public function __construct($source, $currentPage, $pageSize = 20, $conditions = null, $sortby = null, $basePageIndex = 0)
public function findAll($fields = '*', bool $queryLinks = true): array
public function getPagerData(bool $returnPageNumbers = true): array
}namespace FLEA\Helper;
class Verifier
{
public static function checkAll(array &$data, array &$rules, $skip = 0): array
public static function check($value, &$rule): bool|string
}namespace FLEA\Helper;
class Image
{
public static function createFromFile(string $file): self
public function saveAsJpeg(string $file, int $quality = 80): void
public function saveAsPng(string $file): void
public function saveAsGif(string $file): void
}namespace FLEA\Helper;
class FileUploader
{
public array $files;
public int $count;
public function existsFile(string $inputName): bool
public function getFile(string $inputName): ?File
public function check(string $inputName, array $rules): array
public function move(string $inputName, string $targetDir): ?string
public function batchMove(string $inputName, string $targetDir): array
}namespace FLEA\Helper;
class ImgCode
{
public string $code;
public int $expired;
public string $imagetype = 'jpeg';
// 生成验证码
public function generate(int $type = 0, int $length = 4, int $lefttime = 900): void
// 获取图像二进制内容(配合 View 使用)
public function getImageData(?array $options = null): string
// 获取 Content-Type
public function getContentType(): string
// 验证
public function check(string $code): bool
public function checkCaseSensitive(string $code): bool
public function clear(): void
// 工具方法
public static function hex2rgb(string $color, string $default = 'ffffff'): array
}新用法(配合 View):
// 控制器返回验证码图像
public function actionCaptcha(): ViewInterface
{
$imgCode = new ImgCode();
$imgCode->generate();
return View::binary(
$imgCode->getImageData(),
'captcha.jpg',
$imgCode->getContentType()
);
}注意:ImgCode 内部使用 flea_context() 存储验证码,支持 Session/Redis 等多种存储方式。
namespace FLEA\Helper;
class Str
{
// 从字符串提取命名参数
public static function extract(string $string, string $pattern, array $options = []): array
}extract() 选项:
| 选项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
delimiters |
array | ['{', '}'] |
自定义分隔符 |
strip_values |
bool | false |
去除提取值的首尾空格 |
case_insensitive |
bool | false |
忽略大小写匹配 |
collapse_whitespace |
bool | false |
压缩连续空白为单个空格 |
用法示例:
// 基本用法
Str::extract('380-250-80-j', '{width}-{height}-{quality}-{format}');
// ['width' => '380', 'height' => '250', 'quality' => '80', 'format' => 'j']
// 提取 URL 路径
Str::extract('/2012/08/12/test.html', '/{year}/{month}/{day}/{title}.html');
// ['year' => '2012', 'month' => '08', 'day' => '12', 'title' => 'test']
// 自定义分隔符
Str::extract('The time is 4:35pm', 'The time is :time', ['delimiters' => [':', '']]);
// ['time' => '4:35pm']
// 忽略大小写
Str::extract('HELLO World', 'hello {name}', ['case_insensitive' => true]);
// ['name' => 'World']FLEA\Config\Defaults加载框架默认配置FLEA::loadEnv()加载.env环境变量FLEA::loadAppInf()加载应用配置(覆盖默认配置)- 环境变量覆盖应用配置
return [
// 数据库
'dbDSN' => [
'driver' => env('DB_DRIVER', 'mysql'),
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', '3306'),
'login' => env('DB_USERNAME', 'root'),
'password' => env('DB_PASSWORD', ''),
'database' => env('DB_DATABASE', ''),
],
// 路由
'dispatcher' => \FLEA\Dispatcher\Simple::class,
'defaultController' => 'Index',
'defaultAction' => 'index',
'actionMethodPrefix' => 'action',
'actionMethodSuffix' => '',
// 视图
// 'view' => \FLEA\View\Simple::class, // 已删除,改用 View::html() 等工厂方法
'viewConfig' => [
'templateDir' => 'App/View',
'cacheDir' => 'cache',
'cacheLifetime' => 900,
'enableCache' => true,
],
// 缓存
'cacheProvider' => \FLEA\Cache\FileCache::class,
// Context(请求上下文)
'contextDriver' => env('CONTEXT_DRIVER', 'session'),
'contextIdentity' => env('CONTEXT_IDENTITY', 'session'),
// 日志
'logEnabled' => env('LOG_ENABLED', false),
'logFileDir' => env('LOG_FILE_DIR', 'cache'), // 日志文件目录
'logFilename' => env('LOG_FILENAME', 'app.log'),
// JWT
'jwtSecret' => env('JWT_SECRET', ''),
'jwtTtl' => (int) env('JWT_TTL', 7200),
];| 异常类 | 说明 |
|---|---|
InvalidArguments |
无效参数 |
MissingArguments |
缺少参数 |
ExpectedClass |
期望的类不存在 |
ExpectedFile |
期望的文件不存在 |
MissingAction |
动作方法不存在 |
MissingController |
控制器不存在 |
NotImplemented |
方法未实现 |
TypeMismatch |
类型不匹配 |
ValidationFailed |
验证失败 |
ExistsKeyName |
键已存在 |
NotExistsKeyName |
键不存在 |
| 异常类 | 说明 |
|---|---|
MissingDSN |
缺少 DSN |
InvalidDSN |
无效 DSN |
InvalidInsertID |
无效插入 ID |
MissingPrimaryKey |
缺少主键 |
PrimaryKeyExists |
主键已存在 |
SqlQuery |
SQL 错误 |
MissingLink |
关联不存在 |
| 异常类 | 说明 |
|---|---|
CheckFailed |
参数检查失败 |
| 异常类 | 说明 |
|---|---|
InvalidACT |
无效的动作 |
InvalidACTFile |
无效的 ACT 文件 |
| 异常类 | 说明 |
|---|---|
UserGroupNotFound |
用户组未找到 |
1. require FLEA.php
↓
2. FLEA::loadEnv() 加载环境变量
↓
3. FLEA::loadAppInf() 加载应用配置
↓
4. FLEA::runMVC()
- FLEA::init() 初始化服务
- 设置时区
- 注册异常处理器
- 初始化缓存目录
- 绑定 Context 到容器
- 初始化 TraceContext(链路追踪)
- 设置响应头
↓
5. Router::dispatch() 匹配路由
- 匹配成功:设置 handler 和 middlewares
- 匹配失败:Response::current() 设置 404(发布 Signal → send)
↓
6. 中间件管道执行(Pipeline::run,void 返回)
- 全局中间件(FLEA::middleware() 注册)
- 路由级中间件(Router::get/post 等注册)
- 中间件通过 Response::current() 操作响应(设置状态/视图/头)
- 短路中间件设置完 Response 后不调用 $next()
↓
7. Dispatcher 解析 controller/action
- 执行 action,返回 ViewInterface
- handleActionResult() 设置到 Response::current()
↓
8. 实例化控制器 → beforeExecute() → actionXxx() → afterExecute()
↓
9. 视图渲染(ViewInterface 设置到 Response)
↓
10. Pipeline 结束
↓
11. FLEA::runMVC() 发布 Signal → Response::send()
↓
12. 输出响应(包含 X-Trace-Id 头)
TraceID 集成点:
- 第 4 步:
TraceContext::init()初始化 TraceID 和 SpanID - 支持从
X-Trace-Id或Traceparent请求头获取外部传入的 trace_id - 日志服务自动记录 TraceID
- 响应头自动返回
X-Trace-Id
新代码(推荐):
namespace App\Controller;
use FLEA\Controller\Action;
use FLEA\View\ViewInterface;
use FLEA\View;
class PostController extends Action
{
public function actionIndex(): ViewInterface
{
$posts = $this->model->getPublishedPosts(10);
return View::html('post/index.php', ['posts' => $posts]);
}
}旧代码(已废弃,需改写):
// 旧代码 - 需要改写
$this->getView()->assign('posts', $posts);
$this->getView()->display('post/index.php');
// 改为
return View::html('post/index.php', ['posts' => $posts]);namespace App\Model;
use FLEA\Db\TableDataGateway;
class Post extends TableDataGateway
{
public string $tableName = 'posts';
public string $primaryKey = 'id';
public function getPublishedPosts(int $limit = 10, int $offset = 0): array
{
return $this->findAll(['status' => 1], 'created_at DESC', [$limit, $offset]);
}
}use FLEA\Middleware\MiddlewareInterface;
class MyMiddleware implements MiddlewareInterface
{
public function handle(callable $next): void
{
// 前置处理
$next(); // 调用下一个中间件或处理器
// 后置处理
}
}
// 短路中间件示例
class AuthMiddleware implements MiddlewareInterface
{
public function handle(callable $next): void
{
if (!$this->isAuthenticated()) {
\FLEA\Response::current()
->withStatus(401)
->setView(\FLEA\View::json(['error' => 'Unauthorized']));
return; // 不调用 $next,短路
}
$next();
}
}use FLEA\View\ViewInterface;
class TwigView implements ViewInterface
{
public function assign($key, $value = null): void {}
public function display(string $template): void {}
public function fetch(string $template, ?string $cacheId = null): string {}
}use FLEA\Context\DriverInterface;
class CustomDriver implements DriverInterface
{
public function get(string $key, mixed $default = null): mixed { }
public function set(string $key, mixed $value, ?int $ttl = null): bool { }
public function remove(string $key): bool { }
public function has(string $key): bool { }
}| 类型 | 约定 | 示例 |
|---|---|---|
| 控制器 | 首字母大写 + Controller | PostController |
| 模型 | 表名单数形式 | Post |
| 动作方法 | action + 驼峰 | actionIndex() |
| 视图文件 | {controller}/{action}.php |
post/index.php |
标准模式:index.php?controller=Post&action=view&id=1
PATHINFO 模式:index.php/Post/view/id/1
URL 重写模式:/Post/view/id/1
| 类型 | 约定 | 示例 |
|---|---|---|
| 时间戳 | created_at, updated_at |
|
| 主键 | id 或 {table}_id |
|
| 外键 | {table}_id |
框架使用了以下 PHP 7.4 特性:
- 属性类型声明:
public string $tableName - 可空类型:
public ?string $sort = null - 箭头函数:
fn($x) => $x * 2 - 空合并运算符:
$value ?? $default - 联合类型:
int|bool
| 依赖 | 版本 | 说明 |
|---|---|---|
| PHP | 7.4+ | 运行环境 |
| psr/log | ^1.0 | 日志接口 |
| psr/container | ^2.0 | 容器接口 |
| psr/simple-cache | ^1.0 | 缓存接口 |
| vlucas/phpdotenv | ^5.5 | 环境变量加载 |
Response + 中间件集成(门面/适配器模式):
- 新增
FLEA\Internal\Signal内部发布/订阅机制 - 新增
FLEA\HttpResponse响应数据容器 + 实际发送逻辑 - 重构
FLEA\Response为门面(Facade)模式:单例入口、信号订阅、委托调用 - 中间件接口和 Pipeline 保持稳定(
handle()保持void,Pipeline::run()保持void) - 重构
FLEA::runMVC():Pipeline void 返回后通过Response::current()发布信号并发送
View + Response 架构重构:
- 新增
ViewInterface和StreamingViewInterface接口 - 新增 9 个具体 View 类:
FileTemplateView、JsonView、CsvView、RedirectView、BinaryView、SseView、CallbackView、CallbackViewBuilder、NullView - 新增
RendererConfig和SimpleRenderer渲染器组件 - 新增
View工厂类(12 个静态方法) - 重构
Response类支持ViewInterface - 重构
Dispatcher兼容旧代码 - 删除已废弃的
Simple视图类(旧代码需改用View::html()等工厂方法) - 删除
SendFile.php(功能由View::binary()覆盖) - 重构
ImgCode类,新增generate(),getImageData(),getContentType()方法
Bug 修复:
- 修复链路追踪相关问题
新功能:
- 新增链路追踪组件
TraceContext,支持分布式追踪 - 新增
generate_traceid()全局函数 - 日志自动集成 TraceID
- 响应头自动添加
X-Trace-Id - 支持 W3C
Traceparent请求头格式 - 新增 MICROSERVICES.md 微服务开发指南
- 新增
Str::extract()字符串工具,支持命名参数提取
Bug 修复:
- 修复 SqlStatement 类型检测问题,使用
instanceof PDOStatement准确判断 - 非法类型时抛出
TypeMismatch异常 - 修复 Defaults.php 中
sys_get_temp_dir()命名空间错误
重构:
- 移除废弃的
requestFilters和autoLoad配置项 - 移除 Response.php 中多余的 X-Trace-Id 输出逻辑
- 移除 Simple 视图构造函数中的日志,改为在 fetch() 方法中记录渲染的视图文件
重大重构:
- 新增 PSR-11 容器 (
Container) - 新增 PSR-16 缓存 (
Cache) - 新增路由器 (
Router/Route) - 新增中间件系统 (
Middleware/Pipeline) - 新增 HTTP 封装 (
Request/Response) - 新增 JWT 认证 (
Auth/Jwt) - 新增 Context 上下文组件(替代 Session)
- 移除
Ajax.php - 移除
WebControls.php - 移除
ActiveRecord.php - 移除
Session/Db.php
配置变更:
- 新增
cacheProvider配置项 - 新增
jwtSecret/jwtTtl配置项 - 新增
contextDriver/contextIdentity配置项
目录结构变更:
src/FLEA/FLEA/→src/FLEA/src/FLEA/FLEA.php→src/FLEA.phpsrc/FLEA/Functions.php→src/Functions.php
- PSR-4 自动加载
- PHP 7.4 类型声明
- PSR-3 日志接口
create()方法返回类型改为int
- CLAUDE.md - 开发规范
- CHANGES.md - FLEA 目录代码修改记录
- APP_CHANGES.md - App 目录代码修改记录
- GIT_COMMIT.md - Git 提交记录