muzk6 / jdi
PHP Framework
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-03-24 07:42:02 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::getREMatches
URL 正则捕获项\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.php
config/common/app.php
vendor/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.log
PHP 标准错误处理程序写的日志,比较精简,但能记录 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')
跳转到/demo
alert()
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
, 即可记录大于等于指定耗时的请求