# hosp-php **Repository Path**: edwardcho/hosp-php ## Basic Information - **Project Name**: hosp-php - **Description**: 基于Composer的PHP MVC框架,实现指定的方案,降低二开难度,统一代码。 该工具库有自己的开发规范,会影响项目原本的规范,请酌情使用。 - **Primary Language**: PHP - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2022-08-08 - **Last Updated**: 2026-09-08 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # hosp-php > php a single file mvc framework **hosp** 是一个 **单文件 PHP MVC 框架**:整个框架只有 `hosp.php` 一个文件,复制即用,无需安装依赖(除 PDO/JSON/GD 扩展外)。 它把「路由 → 校验 → 控制器 → 模型/数据库 → 响应」整套流程压缩进一个文件,适合快速开发中小型站点、内部管理系统与接口服务。 --- ## 特性 - **单文件**:框架本体只有 `hosp.php`,拷贝进项目即可运行。 - **函数式 API**:全部能力以 `hosp\*` 命名空间下的函数提供(`config()`、`action()`、`db_select()`、`hosp()` …),无类继承、无容器。 - **零配置启动**:内置默认配置,按需用 `config()` 覆盖。 - **双模式数据库访问**: - `db_*` 系列函数(直白的过程式 CRUD); - **Hosp 表达式**(`hosp('/user/selectById', $params)`),用一句表达式生成 SQL。 - **自动能力**:自动时间戳、自动补全插入、软删除、自动关联查询、表前缀。 - **路由重写**:支持精确/前模糊/后模糊/全模糊四种匹配,支持转发到闭包或外部 URL。 - **入参自动过滤 + 校验器**:全局过滤器 + 按接口声明的校验规则。 - **事件钩子**:12 个生命周期钩子(路由、权限、Action、SQL、响应…)。 - **视图与分页**:`view()` / `assign()` / `paging()`。 - **内置常用能力**:Session 封装、日志、图片验证码、文件上传、用户/角色/权限(示例版)。 --- ## 环境要求 | 项目 | 要求 | | --- | --- | | PHP | >= 7.0(使用了 `ReflectionFunction`、`list()` 短语法等) | | 扩展 | `pdo`、`json`、`gd`(验证码/图片处理) | | 数据库 | MySQL(PDO 驱动) | | 开发依赖(可选) | `phpunit ^6.5`(仅运行测试需要) | ```bash composer install # 仅安装 phpunit,框架本身不需要构建 ``` --- ## 目录结构 ``` hosp-php/ ├── hosp.php # 框架本体(唯一必需文件) ├── composer.json # 仅声明扩展与开发依赖 ├── phputil.xml # PHPUnit 配置 ├── tests/ # 单元测试(Hosp 表达式、Db、路由、视图…) │ ├── bootstrap.php │ └── HospTest/ └── example/ # 完整示例项目(后台 + 前台) ├── hosp.php # 示例使用的框架版本(含视图/用户/权限/验证码) ├── admin.php # 后台入口 ├── custom.php # 用户自定义配置与业务加载 ├── hosp.sql # 示例数据库 ├── static/ # 静态资源 └── php/ ├── admin/{config.php,action.php,view/} └── home/ ``` --- ## 快速开始 ### 1. 放置框架 把 `hosp.php` 放到站点根目录(如 `index.php` 同级)。 ### 2. 创建入口文件 入口文件名即入口标识(框架从 `REQUEST_URI` 中识别 `.php` 得出 `ENTRANCE_FILE`): ```php 框架在 `_run()` 中会自动尝试加载 `ROOT_PATH/hosp/index.php`(`ROOT_PATH` = `$_SERVER['DOCUMENT_ROOT']`), > 因此也可以把业务代码放在 `hosp/index.php` 中,无需在入口里写 `require`。 ### 3. 启动 ```bash php -S 127.0.0.1:8080 -t . ``` 访问 `http://127.0.0.1:8080/index.php/index/index` → 命中默认控制器/方法 `index/index`。 (`example/` 下的示例入口为 `admin.php`,访问 `http://127.0.0.1:8080/admin.php/index/index`) ### 4. 写第一个接口 ```php db_select('user', $where, '', 'ctime DESC', '', "$start,$size"), 'total' => db_count('user', $where), ]); }); ``` - Action 的形参名会与入参自动匹配(反射注入),缺省值直接作为默认值; - 返回 `success()` / `error()` 即输出统一 JSON:`{"code":0,"msg":"ok","data":{...}}`。 --- ## 路由 URL 规则:`/入口.php/控制器/方法`,`.html` / `.htm` 后缀会被自动剥离(伪静态友好)。 | URL | 解析结果 | | --- | --- | | `/index.php` | `index/index`(`app.default_controller` + `app.default_method`) | | `/index.php/user` | `user/index` | | `/index.php/user/list` | `user/list` | | `/index.php/user/list.html` | `user/list` | ### 路由重写 ```php // 精确:/user/login -> /system/login router('/user/login', '/system/login'); // 后模糊:/user 下任意方法 -> /system/* router('/user/*', '/system/*'); // 前模糊:任意控制器的 login -> /system/login router('*/login', '/system/login'); // 全模糊:所有请求 -> /system/* router('*/*', '/system/*'); ``` `router()` 的目标值还支持: - **闭包**:直接执行并结束请求(适合拦截/重定向); - **外部 URL**(含 `http`):`header('Location: ...')` 跳转,并自动拼接当前入参。 > `*` 表示沿用当前解析出的控制器/方法名。 --- ## 配置 所有配置通过 `config()` 读写,支持 `.` 号层级,数组值会**合并**而非覆盖: ```php config('app.debug', false); // 读:config('app.debug') config('database', ['host' => '127.0.0.1']); // 写(合并) ``` ### `app` 应用 | 键 | 说明 | | --- | --- | | `debug` | 调试模式,开启后显示所有错误 | | `default_filter` | 全局入参过滤器,逗号分隔,默认 `htmlspecialchars_decode,trim` | | `default_type` | 默认响应类型,默认 `json` | | `log.path` / `log.require` | 日志目录与最低记录等级 | | `default_controller` / `default_method` | 默认控制器与方法,默认 `index` / `index` | | `extra_custom` | 需要额外加载的用户文件列表(示例版) | | `custom_action` | 是否完全自定义 action(示例版) | | `token.switch` / `token.generator` | 请求 Token 开关与生成器(示例版) | ### `database` 数据库 ```php config('database', [ 'type' => 'mysql', 'host' => '127.0.0.1', 'port' => '3306', 'dbname' => 'hosp', 'username' => 'root', 'password' => 'root', 'table_prefix' => 'hp_', // 表名前缀 'charset' => 'utf8mb4', // 软删除:表名.字段 => 未删除时的值 'soft_delete' => ['timeline.dtime' => 0], // 软删除写入值(支持闭包,或按表配置数组) 'soft_delete_value' => function () { return time(); }, // 自动能力 'auto_event' => [ 'complete_insert' => true, // 插入时自动补全字段默认值 'timestamp' => [ // 自动时间戳 'timeline' => [ 'create_time' => 'ctime', 'update_time' => 'mtime', ], ], // 自动关联查询:主表.外键 => 关联表.字段 'query' => ['user.role_id' => 'role.id'], ], ]); ``` > 说明:根目录 `hosp.php`(精简版)中上述自动能力的配置键为 `database.declare`(含 `no_null`、`complete_insert`、`auto_timestamp`、`relation`), > `example/hosp.php`(完整版)中为 `database.auto_event`。二者语义相同,按你使用的版本选择对应键名。 ### `view` 视图 ```php config('view', [ 'path' => APP_PATH . '/php/admin/view', 'ext' => 'php', 'layout' => ['top' => 'layout/top', 'bottom' => 'layout/bottom'], 'common' => function () { /* 每次渲染视图前自动执行,用于全局注入变量 */ }, ]); ``` ### 其他 - `router`:路由规则表; - `action`:接口(控制器方法)注册表; - `validate`:接口入参校验规则; - `event`:生命周期钩子; - `authority`:角色权限(`access` / `except`); - `url`:URL 别名映射,供 `url()` 使用; - `hook` / `listen`:自定义钩子与监听者; - `log`:见 `app.log`。 --- ## 控制器(Action) ```php action('/timeline/edit', function ($id = 0, $title = '', $content = '') { if (is_post()) { $data = compact('title', 'content'); return $id ? (db_update('timeline', $data, 'id = ' . $id) !== false ? success('操作成功') : error('编辑失败')) : (db_insert('timeline', $data) ? success('操作成功') : error('编辑失败')); } assign('entity', $id ? db_get('timeline', $id) : []); return view('edit_timeline'); }); ``` - `action($express, Closure)` 注册;`action($express, $params)` 调用; - 闭包参数按**名称**与入参自动绑定,找不到则使用默认值; - 返回值交给 `_end()` 输出:`success()` / `error()`(JSON)、字符串(HTML)、`view()`(渲染视图文件)。 --- ## 请求与响应 | 函数 | 说明 | | --- | --- | | `input($name = null, $default = null)` | 获取入参,留空返回全部;优先级 `json > post > get > URL 中的参数` | | `is_get()` / `is_post()` | 请求方法判断 | | `url($url, $params = [], $extendParams = false)` | 生成 URL,支持别名与继承当前入参 | | `success($msg = '', $data = [], $code = 0)` | 成功响应 | | `error($msg = '', $code = 1)` | 失败响应 | | `dump($data, $return = false)` | 打印/返回 | --- ## 视图 ```php assign('list', $list); // 注入变量 has_assign('list'); // 是否已注入 paging($count, $page, $size); // 计算分页信息 return view('list_timeline'); // 渲染 view 目录下的 list_timeline.php ``` `paging()` 生成的结构: ```php [ 'total' => 100, 'total_page' => 7, 'page' => 1, 'size' => 15, 'first' => false, 'final' => true, 'prev' => false, 'next' => true, 'list' => [1, 2, 3, 4, 5], ] ``` --- ## 数据库 ### CRUD | 函数 | 说明 | | --- | --- | | `db_exec($sql)` | 执行原生 SQL(记录日志 + 触发 SQL 钩子) | | `db_insert($table, $data)` | 插入一条,返回影响行数 | | `db_insert_all($table, $list)` | 批量插入 | | `db_update($table, $data, $where)` | 更新 | | `db_delete($table, $where)` | 删除(自动判断软删/真删) | | `db_select($table, $where, $field, $order, $group, $limit, $having)` | 查询多条 | | `db_get($table, $id, $field = '', $pk = 'id')` | 查询单条 | | `db_field($table, $where, $field)` | 查询单个字段值 | | `db_column($table, $where, $field)` | 查询某列(一维数组) | | `db_count($table, $where, $count = '*')` | 统计数量 | | `db_table($table, $prefix = null)` | 拼接带前缀的表名 | | `db_true_delete($table, $bool = '')` | 设置/读取下一次是否真删 | | `db_allow_fields($table, $bool = null)` | 下一次操作是否自动剔除非表内字段 | | `db_error($error = '')` | 读取/写入最后一次数据库错误 | | `mysql_history($sql = '')` / `get_last_sql()` | SQL 执行历史 / 最近一条 SQL | ```php $list = db_select('timeline', 'isuse = 1', 'id,title', 'ctime DESC', '', '0,15'); $count = db_count('timeline', 'isuse = 1'); $row = db_get('timeline', 12); $name = db_field('timeline', 'id = 12', 'title'); ``` **自动关联查询**:配置 `database.auto_event.query`(或 `database.declare.*.relation`)后,`db_select()` / `db_get()` 的每条记录会自动附带 `relation` 字段: ```php config('database.auto_event.query', ['user.role_id' => 'role.id']); $user = db_get('user', 1); $user['relation']['role']; // 关联查询出的角色记录 ``` --- ## Hosp 表达式 `hosp($express, $params)` 是框架的数据库访问核心:用一句表达式描述一次数据库操作,框架解析出 SQL 并执行。 格式:`/表名/操作表达式[?查询串参数]` ### 简易表达式(驼峰) ```php hosp('/user/insert', ['name' => 'tom']); // INSERT hosp('/user/deleteByIdName', ['id' => 1, 'name' => '2']); // DELETE ... WHERE id AND name hosp('/user/updateByIdSetNickname', ['id' => 1, 'nickname' => 'x']);// UPDATE ... SET nickname WHERE id hosp('/user/selectById', ['id' => 1]); // SELECT ... WHERE id hosp('/dict/updateByKeySetValue', ['key' => 'k', 'value' => 'v']); hosp('/dict/select', []); // 全表查询 ``` 参数也可以直接写在表达式后面(支持 `?a=1&b=2` 查询串形式): ```php hosp('/user/selectById?id=1'); ``` 关键字:`By`(条件)、`Set`(更新字段)、`Only`(指定字段)、`None`(排除字段)、`Order`(排序,`!` 前缀表示倒序)、`Group`(分组)。 类型后的第一个非关键字单词作为**模式**,例如 `selectOne`、`selectCount`、`selectList`。 ### 标准表达式(`{...}`) ```php $express = 'selectList{ by[user_id:user_ids|name] order[ctime,!id] none[id,nickname] only[user_id,nickname] group[id,name] having[user_id:user_id2&(name:name2|name3)] limit[start,limit] }'; hosp('/user/' . $express, $params); ``` 子表达式: | 子表达式 | 作用 | | --- | --- | | `by[...]` | WHERE 条件 | | `set[...]` | UPDATE 的字段 | | `only[...]` | 指定查询字段 | | `none[...]` | 排除字段 | | `order[...]` | 排序,`!` 前缀为 `DESC` | | `group[...]` | 分组 | | `having[...]` | HAVING 条件 | | `limit[...]` | 分页/条数 | 条件符号: | 符号 | 含义 | 示例 | | --- | --- | --- | | `&` | AND | `by[id&name]` | | `\|` | OR | `by[id\|name]` | | `!` | 不等于 | `by[!id]` | | `~` | `LIKE '%x%'` | `by[~name]` | | `%~` | `LIKE '%x'` | `by[%~name]` | | `~%` | `LIKE 'x%'` | `by[~%name]` | | `!~` / `!%~` / `!~%` | 对应的 NOT LIKE | `by[!~name]` | | `#` / `!#` | `IN` / `NOT IN` | `by[#id]` | | `( )` | 括号分组 | `delete{by[(id&name)\|nickname]}` | | `field:param` | 字段与入参名不一致时指定参数名 | `by[user_id:user_ids]` | 表达式中的变量以 `#[参数名]` 形式占位,执行前由入参替换。 --- ## 校验器 按接口(或校验器名)声明校验规则,在 Action 执行前自动生效: ```php config('validate', [ 'edit_user' => ['/user/info', '/user/user'], // 校验器 => 生效的接口 ]); config('validate./user/info', [ 'id' => [REQ, INT], 'type' => [1, 2, 3], // 枚举合法值 'name' => [REQ], 'email' => [EMAIL], ]); ``` 支持的类型常量:`REQ`(必填)、`INT`、`FLOAT`、`MOBILE`、`EMAIL`、`ARR`。 校验失败直接返回 `{"code":1,"msg":"xxx参数是必填的"}` 并终止请求。 --- ## 事件钩子 在 `config('event')` 中注册闭包即可介入生命周期: | 钩子 | 时机 | 参数 | | --- | --- | --- | | `after_init` | 框架初始化完成 | — | | `before_router` / `after_router` | 路由解析前后 | `$route` | | `before_authority` / `after_authority` | 权限校验前后(示例版) | `$action`, `$result` | | `before_action` / `after_action` | Action 调用前后 | `$action`, `$input` / `$output` | | `before_model` / `after_model` | 模型调用前后 | `$model`, `$input` / `$output` | | `before_hosp` / `after_hosp` | Hosp 表达式执行前后 | `$express`, `$input` / `$output` | | `before_sql` / `after_sql` | SQL 执行前后 | `$sql`, `$result` | | `before_complete` | 响应输出前 | `$response` | ```php config('event.before_sql', function ($sql) { // SQL 审计 / 慢查询统计 }); ``` 另外,`listen` 可为一个事件注册多个监听者,用 `event($name, $args)` 触发;`hook($name, Closure)` 可注册自定义钩子。 --- ## 权限(示例版) ```php config('authority', [ 'access' => function () { return [ 'guest' => [], 'admin' => ['/index/index', '/timeline/list', '/timeline/edit'], ][user_role() ?: 'guest']; // true 表示全部权限,false/[] 表示无权限 }, 'except' => ['/login/index', '/logout/index'], // 不拦截 ]); ``` 配套用户函数:`user_id()`、`user_role()`、`user_login($id, $role)`、`user_logout()`。 --- ## 内置工具 | 函数 | 说明 | | --- | --- | | `config($name, $value)` | 配置读写 | | `session($name, $value)` | Session 读写(统一挂在 `$_SESSION['hosp']` 下,支持 `.` 层级) | | `log($content, $filename)` | 写日志,按 `年/月/日` 分目录 | | `assign()` / `has_assign()` / `view()` / `paging()` | 视图与分页 | | `upload_file_move()` / `upload_file_name_ext()` / `upload_file_valid()` | 文件上传 | | `verify_code_image()` / `verify_code_save()` / `verify_code_check()` | 图片验证码 | | `dump()` / `true()` / `false()` | 调试与结果包装(`[true, $data]` / `[false, $msg]`) | 内置接口(示例版 `config('action')` 中已注册): `/system/upload_image`、`/system/verify_code_image`、`/system/register`、`/system/login`、`/system/logout`、`/system/check_login`。 --- ## 常量 | 常量 | 说明 | | --- | --- | | `DS` `ROOT_PATH` `APP_PATH` `UPLOAD_PATH` | 路径相关 | | `ENTRANCE` `ENTRANCE_FILE` `ACTION` | 当前入口与当前接口(如 `/user/list`) | | `ID` `TIME` `MICRO_TIME` | 本次请求唯一 ID 与时间 | | `REQ` `INT` `FLOAT` `MOBILE` `EMAIL` `ARR` | 校验类型 | | `LOG_NORMAL` `LOG_WARN` `LOG_ERROR` | 日志等级 | | `HTTP_CODE_SUCCESS` `HTTP_CODE_FAIL` `HTTP_CODE_ERROR` `HTTP_CODE_FORBIDDEN` | 响应码 | --- ## 测试 ```bash composer install ./vendor/bin/phpunit -c phputil.xml ``` 测试用例位于 `tests/HospTest/`,覆盖 Hosp 表达式解析、数据库操作、路由、视图、验证码等,同时可直接作为用法示例阅读。 --- ## 示例项目 `example/` 是一个可直接运行的后台 + 前台站点: 1. 导入 `example/hosp.sql` 到 MySQL; 2. 在 `example/custom.php` 中修改数据库连接(或新建 `pro` 文件切换到生产配置); 3. 把 Web 根目录指向 `example/`; 4. 访问 `admin.php/login`(默认配置见 `example/php/admin/config.php`)。 --- ## 许可证 [MIT](LICENSE) © Edward Cho