muzk6 / jdi
PHP Framework
Installs: 117
Dependents: 0
Suggesters: 0
Security: 0
Stars: 4
Watchers: 2
Forks: 0
Open Issues: 0
pkg:composer/muzk6/jdi
Requires
- php: ^7.1
- ext-json: *
Suggests
- ext-pcntl: Required to use the Pcntl
- ext-pdo: Required to use the PDO MySql
- ext-redis: Required to use the Redis
- ext-xdebug: Required to use the Xdebug
- php-amqplib/php-amqplib:^2.9: Required to use the Message Queue
- phpunit/phpunit: Required to use the Unit testing
- tideways_xhprof: Required to use the tideways_xhprof
- dev-master
- v1.7.0
- v1.6.2
- v1.6.1
- v1.6.0
- v1.5.5
- v1.5.3
- v1.5.2
- v1.5.1
- v1.5.0
- v1.3.10
- v1.3.9
- v1.3.8
- v1.3.6
- v1.3.5
- v1.3.3
- v1.3.2
- v1.3.1
- v1.3.0
- v1.2.18
- v1.2.17
- v1.2.16
- v1.2.14
- v1.2.13
- v1.2.12
- v1.2.11
- v1.2.10
- v1.2.9
- v1.2.8
- v1.2.7
- v1.2.6
- v1.2.5
- v1.2.3
- v1.2.2
- v1.2.1
- v1.2.0
- v1.1.3
- v1.1.2
- v1.1.1
- v1.1.0
- v1.0.2
- v1.0.1
- v1.0.0
This package is auto-updated.
Last update: 2025-10-31 14:09:35 UTC
README
PHP 框架 —— Just Do It
相关项目:
jdi-ops: JDI 框架的 OPS 运维后台
起步
安装
composer require muzk6/jdi
基本用例
框架用例
作为框架使用
index.php
require __DIR__ . '/vendor/autoload.php'; // 框架初始化 \JDI\App::init(); // 注册路由 route_get('/', function () { return 'Just Do It!'; }); // 分发路由 route_dispatch();
开启服务:php -S 0.0.0.0:8080
库用例
无需\JDI\App::init(),可以直接作为库嵌入到现有项目使用
参数配置
自定义配置:\JDI\App::init(['config.debug' => false]);
其它配置项参考下表:
| 配置项 | 默认值 | 描述 |
|---|---|---|
| config.debug | true | 调试开发模式,用于显示错误信息、关闭视图模板缓存、关闭 opcache |
| config.path_data | <jdi 根目录>/data | 数据目录,保证有写权限 |
| config.path_view | <jdi 根目录>/views | 视图模板目录 |
| config.path_config_first | <空> | 第一优先级配置目录,找不到配置文件时,就在第二优先级配置目录里找,以此类推 |
| config.path_config_second | <空> | 第二优先级配置目录 |
| config.path_config_third | <jdi 根目录>/config | 第三优先级配置目录,一般默认即可,取框架的默认配置文件 |
| config.timezone | PRC | 时区 |
| config.session_start | true | 开启 session |
| config.init_handler | null | 容器初始化回调,null 时默认调用 \JDI\App::initHandler |
更多例子请 cd 到目录 tests/feature
- 简单路由
php -S 0.0.0.0:8080 router_simple.php - 中间件与路由
php -S 0.0.0.0:8080 router_advanced.php - 经典 MVC
php -S 0.0.0.0:8080 router_mvc.php
命名规范(仅仅建议)
- 变量名下划线,与数据库字段、数组键、URL中的参数统一
- 方法名小驼峰,与面向对象统一
- 函数名下划线,与面向过程、内置函数统一
路由
注册路由
route_get()注册回调 GET 请求route_post()注册回调 POST 请求route_any()注册回调任何请求route_middleware()注册路由中间件,顺序执行,组内优先route_group()路由分组,隔离中间件
其中参数 url: /demo 全匹配;#/demo# 正则匹配(#开头自动切换为正则模式);更多高级用法可使用 \JDI\Services\Router::addRoute
用例
中间件
route_middleware(function () { echo '中间件a1'; // 这里为了测试才用 echo,实际开发时只用于处理逻辑而不需要打印 }); route_middleware(function () { echo '中间件a2'; }); route_group(function () { route_middleware(function () { echo '中间件b1'; }); route_get('/mid', function () { return 'Just Do It!'; }); }); route_middleware(function () { echo '中间件a3'; });
GET 请求 /mid 输出:
中间件b1
中间件a1
中间件a2
Just Do It!
中间件a3
异常处理
// 不指定参数三,用默认方式 route_post('/xhr', function () { panic(); }); // 指定参数三,自定义异常处理 route_post('/doc', function () { panic('doc error'); }, function (Exception $exception) { if ($exception instanceof AppException) { // 只提示业务异常 alert($exception->getMessage()); } else { // 非业务异常不提示 back(); } });
POST 请求 /xhr 输出:{ "state": false, "code": 0, "message": "", "data": {} }
POST 请求 /doc 输出 alert() 弹层:doc error
模拟请求本地路由
route_get('/idnex', function() { return [input('get.da:i'), svc_auth()->isLogin()]; }); route_simulate('/idnex', ['da' => 10], 1010); // 注意在这之前不能调用 route_dispatch()
路由的其它方法
\JDI\Services\Router::setStatus404Handler设置响应 404 的回调函数\JDI\Services\Router::fireStatus404触发 404 错误\JDI\Services\Router::getMatchedRoute成功匹配的路由\JDI\Services\Router::getREMatchesURL 正则捕获项\JDI\Services\Router::getException异常,通常用于在后置中间件做处理\JDI\Services\Router::setResponseContent设置响应内容,通常用于在后置中间件改写响应内容\JDI\Services\Router::getResponseContent获取响应内容
请求参数
获取、过滤、表单验证、类型强转 请求参数
$_GET,$_POST支持payload
- 以下的验证失败时会抛出异常 \JDI\Exceptions\AppException
不验证,一个一个获取
$first_name = input('first_name'); $last_name = input('last_name'); var_dump($first_name, $last_name);
不验证,统一获取
$request = request(); var_dump($request);
部分验证,一个一个获取
$first_name = input('first_name'); $last_name = validate('last_name')->required()->get('名字'); var_dump($first_name, $last_name);
部分验证,统一获取
validate('last_name')->required()->setTitle('名字'); $request = request(); var_dump($request);
串联短路方式验证(默认)
遇到验证不通过时,立即终止后面的验证
validate('first_name')->required(); validate('last_name')->required()->setTitle('名字'); $request = request(); // 以串联短路方式验证 var_dump($request);
串联结果
{
"state": false,
"code": 10001000,
"message": "参数错误",
"data": {
"first_name": "不能为空"
}
}
并联验证
即使前面的验证不通过,也会继续验证后面的字段
validate('first_name')->required(); validate('last_name')->required()->setTitle('名字'); $request = request(true); // 以并联方式验证
并联结果
{
"state": false,
"code": 10001000,
"message": "参数错误",
"data": {
"first_name": "不能为空",
"last_name": "名字不能为空"
}
}
input() 参数说明
'get.foo:i' 中的类型转换i为整型,其它类型为:
| Name | Type |
|---|---|
| i | int |
| s | string |
| b | bool |
| a | array |
| f | float |
| d | double |
响应内容
在路由回调里使用
return 'Just Do It!';Just Do It!return [];{ "state": true, "code": 0, "message": "", "data": {} }return ['foo' => 1];{ "state": true, "code": 0, "message": "", "data": { "foo": 1 } }return api_msg('保存成功');{ "state": true, "code": 0, "message": "保存成功", "data": {} }panic();{ "state": false, "code": 0, "message": "", "data": {} }panic('保存失败');{ "state": false, "code": 0, "message": "保存失败", "data": {} }panic('保存失败', ['foo' => 1]);{ "state": false, "code": 0, "message": "保存失败", "data": { "foo": 1 } }panic(10001000);{ "state": false, "code": 10001000, "message": "参数错误", "data": {} }; 参考翻译文件 lang_zh_CN.phppanic([10002001, 'name' => 'tom']);{ "state": false, "code": 10002001, "message": "欢迎 tom", "data": {} }
以上方式都是 return api_json() 的衍生,更多需求可直接调用 api_json()
其中与 api_format() 的关系是:api_json() 即 json_encode(api_format())
容器服务
定义单例服务
function svc_foo() { return \JDI\App::singleton(__FUNCTION__, function () { return new Foo(); }); }
覆盖
如果上面的 svc_foo() 还未调用过,可以覆盖:
\JDI\App::set('svc_foo', function () { return new Bar(); });
如果要强制修改,可以先删除:
\JDI\App::unset('svc_foo');
PDO 数据库
可以配置 MySQL, SQLite 等 PDO 支持的数据库
- 配置文件
config/mysql.php - 用例参考
tests/phpunit/Services/DBTest.php
如果想同时使用 SQLite 等数据库, 复制 mysql.php 为新的数据库配置文件,按需配置 dsn,再注册容器即可(参考 services.php svc_mysql())
helpers 其它辅助函数用例
config() 配置文件
config('app.lang')- 例如第一、二优先级目录分别是
config/dev/,config/common/ - 依次搜索下面文件,存在时返回第一个结果的文件内容:
config/dev/app.phpconfig/common/app.phpvendor/muzk6/jdi/config/app.php
config(['app.lang' => 'en'])设置 run-time 的配置
trans() 多语言文本
trans(10001000)- 假设当前语言是
zh_CN, 默认语言是en - 依次搜索
lang_zh_CN.php, lang_en.php, 存在10001000这个key时返回第一个结果内容,都不存在时返回''
log_push() 日志推入待刷写集合
log_push('test', ['foo', 'bar'], 'login') 把内容写到data/log/login_20190328.log
各日志文件说明:
standard_xxx.logPHP 标准错误处理程序写的日志,比较精简,但能记录 Fatal Error, Parse Errorerror_xxx.log框架写的错误日志,比较详细app_xxx.log用户写的默认日志,文件名可以修改,由log_push()参数3控制
通用日志字段场景:
log_push('test', ['user_id' => 123, '这里写日志']); log_push('test', ['user_id' => 123, '另一处又写日志']);
如果像以上例子都要记录 user_id, 可以使用 \JDI\Services\Log::setExtraData 单独把 user_id 设置起来,后面调用 log_push() 时不需要再记录 user_id:
svc_log()->setExtraData('user_id', 123); log_push('test', ['这里写日志']); log_push('test', ['另一处又写日志']);
log_push() 还有一个特性是延迟执行(脚本程序结束时才真正写日志),因此上面例子又可以写成:
log_push('test', ['这里写日志']); log_push('test', ['另一处又写日志']); svc_log()->setExtraData('user_id', 123);
手动刷写日志场景:
默认自动刷写,特殊场景(在 register_shutdown_function() 之前就调用了日志有关方法),手动刷写可参考 \JDI\Services\Router::dispatch 里的用法
svc_log()->autoFlush(false); // 关闭自动刷写 svc_log()->flush(); // 手动刷写
自定义日志引擎,参考 \JDI\Support\Svc::log 使用 \JDI\Services\Log::setFlushHandler 的例子
url() 带协议和域名的完整URL
- 当前域名URL:
url('path/to') - 其它域名URL:
url(['test', '/path/to'])
panic() 直接抛出业务异常对象
panic(10001000)等于throw new AppException('10001000')自动转为错误码对应的文本,参考翻译文件 lang_zh_CN.phppanic('foo')等于throw new AppException('foo')panic('foo', ['bar'])等于throw (new AppException('foo'))->setData(['bar'])
AppException 异常属于业务逻辑,能够作为提示通过接口返回给用户看,而其它异常则不会(安全考虑)
request_flash(), old() 记住并使用上次的请求参数
request_flash()把本次请求的参数缓存起来old(string $name = null, string $default = '')上次请求的字段值
csrf_*() CSRF
csrf_field()直接生成 HTMLcsrf_token()生成 tokencsrf_check()效验,token 来源于$_SERVER['HTTP_X_CSRF_TOKEN'], $_POST['_token'], $_GET['_token'], $_REQUEST['_token']
请求时带上 Token, 使用以下任意一种方法
POST请求通过表单参数_token, 后端将从$_POST['_token']读取GET请求通过?_token=, 后端将从$_GET['_token']读取- 通过指定请求头
X-CSRF-Token, 后端将从$_SERVER['HTTP_X_CSRF_TOKEN']读取
flash_*() 闪存,一性次缓存
flash_set(string $key, $value)闪存设置flash_has(string $key)存在且为真flash_exists(string $key)闪存是否存在,即使值为 nullflash_get(string $key)闪存获取并删除flash_del(string $key)闪存删除
assign(), view() 模板与变量
assign('firstName', 'Hello')定义模板变量return view('demo', ['title' => $title])定义模板变量的同时返回渲染内容
back(), redirect(), alert() 网页跳转
back()网页后退redirect('/demo')跳转到/demoalert()JS alert() 并跳转回上一页
登录
svc_auth()->login(1010); // 登录 ID 为 1010 svc_auth()->getUserId(); // 1010 svc_auth()->isLogin(); // true svc_auth()->logout(); // 退出登录
消息队列
- worker 遇到信号
SIGTERM,SIGHUP,SIGINT,SIGQUIT会平滑结束进程。如果要强行结束可使用信号SIGKILL, 命令为kill -s KILL <PID> - 当文件有变动时,队列有消息会触发 worker 退出,因此需要以守护进程方式启动 worker, 建议生产环境使用 supervisor 服务,临时测试可用
watch命令启动 worker
依赖
composer require php-amqplib/php-amqplib:^2.9
配置
config/rabbitmq.php
用例
参考 tests/feature/message_queue.php
建议规则:
- 每个 worker 只消费一个队列;
- 队列名与 worker名 一致,便于定位队列名对应的 worker 文件;
- 队列名与 worker名 要有项目名前缀,防止在 Supervisor, RabbitMq 里与其它项目搞混
OPS 运维与开发
推荐使用本框架的运维后台 jdi-ops
XDebug Trace
跟踪调试日志
日志默认位于 data/xdebug_trace/
依赖
ext-xdebug
跟踪 fpm
- 当前URL 主动开启:
/?_xt=name0,name0是当前日志的标识名 - Cookie 主动开启:
_xt=name0;
注意:URL, Cookie 方式的前提必须先设置 config/whitelist.php 白名单 IP 或 白名单 Cookie
跟踪 cli
php demo.php --trace 在任何脚本命令后面加上参数 --trace 即可
XHProf
日志默认位于 data/xhprof/
依赖
使用
- 配置文件
config/xhprof.php enable设置为1, 即可记录大于等于指定耗时的请求