SW-X
SW-X 是一款基于Swoole 实现的常驻内存便捷开发式PHP框架,专为高性能、便捷开发而生,摆脱传统的FPM运行模式。 在开发上,我们为您准备了以下常用组件:
- http 服务服务器
- websocket 服务服务器
- server 服务服务器
- 数据库ORM
- 图片验证码
- 模板渲染引擎
- 协程redis连接池
- 协程mysql 连接池
- 注解路由
- 注解Ioc
- 注解Aop
- 注解Param
- 双容器实现请求隔离
以上组件为常用组件,更多组件请看组件库文档
维护团队
- 作者
- 小黄牛 1731223728@qq.com
- 团队成员
- 暂无,期待您的加入
其他
- SW-X官方一群 1012826310
-
商业支持:
- QQ 1731223728
-
作者微信
- junphper
QQ交流群
注意事项
- 不运行在代码中执行sleep类型函数,因为这样有可能导致系统堵塞,出现大量等待进程,
- 不能使用exit/die语句,因为这样会导致worker进程重启,这样的后果是其进程下挂载的连接池也会跟着重启。
- 连接池是使用完成之后,必须要return归还连接,否则将造成连接池队列被取空的问题。
约定规范
- 项目中类名称与类文件(文件夹)命名,均为大驼峰,变量与类方法为小驼峰。
- 在HTTP响应中,于业务逻辑代码中echo $var 并不会将$var内容输出至相应内容中,请调用实例中的fetch()方法实现。
SW-X版本更新记录
V1.2.31(2021年02月03)
1、修复Rpc onTask事件多一行错误代码,导致提示警告错误的BUG
2、Rpc客户端加入异步回调通知支持
3、Rpc服务端加入业务层支持主动抛出错误使用$this->rpc_error=true 的方式,表示当次请求为异常请求,并记录到服务中心
4、Rpc服务中心web组件,加入异常请求日志预览功能
V1.2.30(2021年02月01)
1、修复Db->table构造子查询时,表前缀没有自动加上
2、修复Rpc-WEB服务中间组件,删除节点时本地文件缓存没有同步更新的BUG
3、优化RpcClient组件,加入最大延迟设置,当请求超过该值时,客户端将记录请求日志
4、修复Rpc服务中心轮询检测TCP-IP是否故障没带端口的BUG
5、HTTP服务,Request组件,新增一个raw()方法,用于支持接收raw类型的数据流,常用于接收第三方接口回调参数
6、Redis连接池,新增一个prefix()方法,用于支持临时修改前缀标识符
7、修复RpcClient请求次数递归错误的bug
8、RPC服务支持异步任务池投递,当投递异步任务池时,投递成功则返回true
V1.2.29(2021年01月26)
1、修复Db->where()条件对数字的字符串类型判断错误的bug
2、修复Redis组件在传入list命令的时候报错的bug
3、优化Db->where条件在使用二维数组的时候,允许这种格式$where[] = 'id=1';
4、优化de_bug模式,单独对error错误日志写入、SQL日志记录做开关,配置项在app.php文件内
5、修复直接 new \x\Db的情况下没有正常自动回收的BUG
V1.2.28(2021年01月25)
1、修复HTTP控制器使用Ioc注解注入Db、Redis时,没调用return()归还连接,也不会触发__destruct回收的bug
2、修复WebSocket控制器使用Ioc注解注入Db、Redis时,没调用return()归还连接,也不会触发__destruct回收的bug
V1.2.27(2021年01月23)
1、修复RPC多次请求时,参数没递归传递正确的bug
2、修复Redis没使用config前缀的BUG
3、修复RpcClient客户端当设置成多次请求时,没有正确递归的bug
4、修复RpcWeb服务中心,无法修改节点ip和端口的bug
5、优化RpcWeb服务中心,为编辑节点不允许修改节点名称
6、修复DbPool被PDO:MySQL server has gone away误杀的BUG
7、优化DbPool,加入析构函数自动归还链接,减少不规范开发导致的出错概率
8、优化RedisPool,加入析构函数自动归还链接,减少不规范开发导致的出错概率
V1.2.26(2021年01月21)
1、优化Rpc服务,由单机服务中心,改为Redis统一存储服务。
2、优化RpcWeb控制台,改为统一服务中心,Rpc的ping检测改为只在设置为服务中心的应用中启用。
V1.2.25(2021年01月19)
1、修复异常监听当控制器调用类错误时,没有正常显示错误内容的BUG
2、修复HTTP监控当控制器调用类错误时,没有正常记录日志的BUG
3、修复Rpc服务send完成后立刻close导致客户端存在可能数据未正常接受就已经关闭的BUG
4、修复HTTP-Monitor组件修改密码后无法正确登陆的BUG
5、新增HTTP-RPC服务WEB控制台组件
6、新增RPC服务可手动关闭支持
V1.2.24(2021年01月12)
1、修复server服务无法正常启动的bug
2、修复错误异常没正常监听到PHP报错的bug
3、实现RPC微服务支持
V1.2.23(2021年01月08)
1、修复sw-x start时,没有初始化进程PID记录文件的BUG
2、修复HTTP上传文件时,框架getSaveName自动删除了ROOT_PATH,导致没有返回完整的地址
3、修复Param注解,当设置允许为空,并设置了正则表达式等过滤参数时,参数为空时也跑过滤规则的BUG
4、Param注解新增一个method参数,表示当为某个请求类型时,该注解才生效,不填写则默认任何请求都生效,该参数只对HTTP服务有效,WebSocket服务设置无效
V1.2.22(2021年01月07)
1、新增HTTP请求记录WEB监控服务组件
2、优化错误异常监听
3、新增Db连接池小于等于0时异步调用生命周期回调通知
4、新增Redis连接池小于等于0时异步调用生命周期回调通知
5、新增默认时区配置
V1.2.21(2020年12月30)
1、新增一个CMD命令支持,用于生成初始控制器文件
V1.2.20(2020年12月11)
1、紧急修复,APP启动服务前错误载入了路由表,导致reload指令没办法正常重载业务代码
V1.2.19(2020年12月10)
1、新增,HTTP-Request请求类,新增一个is_ajax方法,用于判断当前请求是否为ajax类型
V1.2.18(2020年11月07)
1、修复,容器无法正确存储除对象、闭包函数之外的其他类型数据的bug
2、新增,HTTP调试器,用于监听当前请求的框架处理流程和响应结果,便于调试,只有在
app.de_bug == true
的情况下开启3、修复,自定义注解在服务初始化时,也加载了其他未自定定义的注解标签吗,导致单元测试注解无效的BUG
4、优化,单元测试调试时,路由地址没进行自适应大小写的问题
5、修复,单元测试无法正常调试的BUG
V1.2.17(2020年10月30)
1、新增,TestCase单元测试注解,暂只支持HTTP服务应用
2、新增,Db-ORM新增一个test方法,用于支持TestCase单元测试注解
V1.2.16(2020年10月28)
1、新增,自定义注解功能,所有自定义的注解类均为前置注解,加载顺序在内置环绕注解类之后。
2、新增,Db-ORM支持whereOr操作
3、新增,Db-ORM支持whereIn操作
4、新增,Db-ORM支持whereNotIn操作
V1.2.15(2020年10月27)
1、优化Db的where操作,当为数组条件时,例如$where[] = ['id', 'in(1,2,3)', null];时,null条件不再进行字符串解析。
2、修复Param注解当过滤参数为数组类型时解析错误的BUG
3、删除swoole/library/event/Route.php这个多余文件
4、Mysql/Redis新增获取不到连接池实例时,返回false,该优化主要面对定时任务【定时任务再onstart事件载入,优先级高于连接池载入的实例】
5、修复Client客户端发包,URL带端口号时不能正常发送,errCode为【704】的BUG
6、优化了生命周期controller_error的判断流程,HTTP请求下没办法正确获取报错内容
V1.2.14(2020年9月30)
1、增加,WebSocket服务在open、close阶段记录于销毁请求容器
2、优化了生命周期controller_error的判断流程,修复获取websocket事件错误
3、修复WebSocket服务下,使用param函数无法正确获取参数
4、修复定时任务载入事件,从onStart改为onWorkerStart,只有第一个worker线程启动时载入
5、修复定时器中无法正常使用Mysql、Redis实例
6、修复定时器、Swoole事件中无法调用WebSocket基类的fetch方法,改为最后一个参数加入server实例传入
V1.2.13(2020年8月16)
1、优化Db链:insert、insertGetId、update、setInc、setDec操作,字段名加入``字符串包裹,防止字段名冲突
V1.2.12(2020年8月8)
1、修复生命周期,获取错websocket容器名称,导致没办法回调事件的BUG
2、新增HTTP客户端组件封装,用于代替PHP-FPM-CURL组件
V1.2.11(2020年8月6)
1、server新增一个配置项,package_max_length,修复文件上传不能大于2M的bug
2、修复Client,HTTP请求无法正确调用Swoole原生支持方法的BUG
V1.2.10(2020年8月3)
1、优化Param注解参数预设为真null时也执行,之前是isset为true时才执行
2、修复HTTP文件无法正确上传,返回上传路径错误的BUG
3、Db的select查询失败优化为返回空数组[]
4、修复新版本在onWorkerStart阶段依旧读取老定时任务配置不存在的BUG
5、优化Mysql连接池存活检测,改为15分钟检测50%的连接是否还存活
6、修复Mysql连接池过期,存活检测没有自动补充新连接的BUG
7、修复Ioc注解,初始化类传入参数无法正常解析的BUG
8、修复Ioc注解,调用类方法时传入参数无法正常解析的BUG
9、优化,Ioc注解不再支持对静态控制器方法的使用,规范控制器方法都必须为动态方法,若检测为静态方法,将对route_error生命周期抛出status=Ioc Static的错误
10、新增,Db类支持切换临时数据库连接实例,但其连接为PDO短连接,与连接池无关,同样需要调用return清空实例
V1.2.9(2020年7月30)
1、修复Mysql连接数统计不正确的BUG
2、新增Mysql连接池定时器检测功能,修复长时间没连接,MySQL报 server has gone away的错误
V1.2.8(2020年7月29)
1、修复控制器重定向读取实例错误的BUG
2、控制器重定向301改为默认302
3、修复Db,where条件传入0不能正确解析的BUG
4、修复Db,count条件在不传入field的情况下无法正确获取*的bug
5、修复Model获取表名,rtrim导致的部分表名获取错误的BUG
6、优化Db,where条件数组方式的时候,使用|符号可以让多个字段支持OR操作
7、修复Db,使用同一个实例时,切换不同的数据表不会清空前置条件的bug
8、修复DB,where条件传入空条件时不能正确解析的BUG
9、修复请求级容器某些场景下会出现内存溢出的BUG
10、使用Swoole官方的连接池重写了Mysql连接池,不再支持多库实例,跟最小连接数
11、使用Swoole官方的连接池重写了Redis连接池,不再支持最小连接数
12、Db的query只允许执行原生select查询,查询成功调用返回fetchAll的结果集
13、Db新增一个exec方法,只允许执行原生除select外的SQL语句
V1.2.7(2020年7月26)
1、修复路由绑定时填写大写字母不兼容的BUG
2、修复控制器重复调用fetch输出页面内容会发生致命异常的bug
V1.2.6(2020年7月22)
1、删除部分无用配置项
2、更换新的模板引擎支持
3、修复Db的debug方法无效的问题
V1.2.5(2020年7月21)
1、配置文件加入参数,是否开启连接池统计监听定时器
2、websocket推送失败,加入生命周期回调事件
3、优化致命异常不进行生命周期回调,只有普通异常才回调,因为致命异常在Swoole中已经跳出协程底层,会导致拿不到协程容器。
V1.2.4(2020年7月20)
1、优化服务启动时自动初始化
redis_pool_num.count
和mysql_pool_num.count
文件2、紧急修复Model类无法正确注入表名的BUG
3、紧急修复【写入类型】Mysql连接池创建参数读取错误的BUG
V1.2.3(2020年7月20)
1、紧急修复WebSocket路由无法正确识别的BUG
2、修复Param注解无法正确处理AES加密后的的数据包
3、调整Websocket->param函数直接获取完整json,改为只获取data参数
V1.2.2(2020年7月20)
1、Db的
update
、delete
方法新增判断条件,为无where条件时不执行返回false2、定时任务的注册方式,改为手动挂载在配置文件
/config/crontab.php
文件中3、
sw-x status
中加入当前Mysql连接数、Redis连接数状态、当前Swoole扩展版本、本机CPU最大支持核数4、修复Param注解不支持websocket参数过滤的BUG
5、修复WebSocket服务的已知bug
V1.2.1(2020年7月19)
1、重构了部分底层~
2、新增了双容器实现~
3、请求实例不再在实例之间传递,而是通过请求级容器获取、共享~
4、实现了框架与请求实例之间的解耦
5、实现了请求与控制器之间的解耦
捐赠
您的捐赠是对SW-X项目开发组最大的鼓励和支持。我们会坚持开发维护下去。 您的捐赠将被用于:
- 持续和深入地开发
- 文档和社区的建设和维护
支付宝
微信
通过微信捐赠的朋友,请留言你的侠号
捐赠者列表
- *松鼠
- *猫唇
环境要求
满足基本的环境要求才能运行框架,SW-X 框架对环境的要求十分简单,只需要满足运行 Swoole4.4+ 拓展的条件,并且 PHP 版本在 7.3 以上即可。
基础运行环境
- 保证 PHP 版本大于等于 7.3
- 保证 Swoole 拓展版本大于等于 4.4+
- 需要 pcntl 拓展的任意版本
安装swoole
建议参考Swoole官方文档,毕竟官方是最新的安装方式。
框架安装
- GitHub 求个小心心 (^.^)
国内可以使用阿里的镜像,但可能也会出现包丢失的情况
composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/
删除镜像
composer config -g --unset repos.packagist
Composer 安装
按下面的步骤进行手动安装
(建议使用)
composer require swoolex/swoolex
或者(可能出错)
composer require swoolex/swoolex:^v1.2.27
官方建议:直接在首页,下载最新版本的安装包源码部署即可。
Hello Word
打开根目录的index.php
文件,通过service('http')
设置为HTTP服务,然后再XShell界面通过命令php index.php
启动服务。
如果端口错误,则先修改/config/http.php
配置文件中间的port
参数。
注意
HTTP服务中的控制器是依赖路由模式加载的,对应的路由模式可以再/config/route.php
文件中进行修改。
当前大版本中的路由生成由注解类实现,不支持手动调用Route类注入路由规则。
wwwroot 项目部署目录
----------------------------------
├─app 应用目录
│ └─controller http服务的控制器目录
│ └─Index.php 默认控制器文件
然后我们可以看下默认的控制器代码:
<?php
namespace app\controller;
use x\Controller;
class Index extends Controller
{
/**
* @RequestMapping(route="/", method="get", title="主页")
*/
public function index() {
return $this->fetch('SW-X:Hello Word!');
}
}
反向代理
由于 Swoole Server 对 HTTP 协议的支持并不完整,建议仅将 SW-X 作为后端服务,并且在前端增加 NGINX 或 APACHE 作为代理,参照下面的例子添加转发规则
Nginx
server {
root 绑定个目录,但不建议指定到server的目录,这样不安全;
server_name 你的域名;
# 解决静态文化访问问题
location /public/ {
root 你的静态根地址,不能带public;
}
location / {
proxy_http_version 1.1;
proxy_set_header Connection "keep-alive";
proxy_set_header X-Real-IP $remote_addr;
if (!-f $request_filename) {
proxy_pass http://127.0.0.1:9502;
}
}
}
代理之后,可通过$this->header()
中的x-real-ip
获取客户端真实ip
Apache
<IfModule mod_rewrite.c>
Options +FollowSymlinks
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
#RewriteRule ^(.*)$ index.php/$1 [QSA,PT,L] fcgi下无效
RewriteRule ^(.*)$ http://127.0.0.1:9501/$1 [QSA,P,L]
#请开启 proxy_mod proxy_http_mod request_mod
</IfModule>
框架设计
SW-X当前版本主要是由服务启动,事件监听注册,路由解析,注解绑定这4个部分组成。
服务端事件
SW-X底层默认监听了Swoole-Server的所有消息事件,并在onRequest
和onMessage
事件中进行了二次开发,进而实现路由解析功能。
如果开发者需要二次使用消息事件,SW-X也支持了二次消息事件转发功能,其目录为/app/event/
。
关于调试模式
调试模式开关在/config/app.php
文件中的de_bug
选项,
当开启调试模式时true
,SW-X会实现以下几个功能:
1、将报错写入到/runtime/log/
目录下
2、将所有执行的SQL语句【除了select】都记录到/runtime/sql/
目录下
3、所有view文件都会重新解析
4、v1.2.18版本起,HTTP服务加入了服务调试器,会出现在页面右下角
生命周期
生命周期的概念是在v1.1.x
版本才开始引入,是框架核心在处理业务时的一些回调事件转发。
生命周期处理方法统一存放在:/lifecycle/
目录下,应统一使用run()
方法作为回调入口。
现框架支持以下生命周期回调处理:
annotate_param:注解Param标签校验失败时的独立回调事件(v1.1.5)
controller_error:应用监听错误的回调事件,只回调普通错误异常(v1.1.6)
route_error:当除了Param注解外,其他注解校验失败时的统一回调事件(v1.1.6)
route_start:路由扫描完成时的回调事件(v1.1.6)
websocket_push_error:当WebSocket->Push失败时的回调事件(v1.2.5)
testcase_callback:当单元测试不通过时的回调事件(v1.2.17)
mysql_pop_error:当Mysql连接数小于等于0时,会回调此事件(v1.2.22)
redis_pop_error:当Redis连接数小于等于0时,会回调此事件(v1.2.22)
rpc_error:当客户端RPC请求错误时,会回调此事件(v1.2.24)
内存溢出
在Swoole中很难定位内存溢出的bug点,为了防止内存溢出,在应用中不要直接调用自定义的静态类,若有必要调用,可以先将实例存储在\x\Container
请求级容器中。
而且在sw-x status
指令中,可以查看到当前应用的内存状态和\x\Container
容器长度,
若内存一直飙升,容器长度无法下降的话的,就表示应用代码中发生了溢出现象,需要自行审计代码。
如果担心sw-x status
不准的话,也可以使用linux的free -h
,压测的过程中,开新窗口,查看打印出来的free
有没有被回收,没的话就代表内存溢出了。
关于Swoole内存溢出的问题,之前认为没必要在文档中解析这个问题,但最近有些开发者向我咨询了,我还是觉得有必要讲解下。
1、由于Swoole是常驻内存的,所以静态类 或 静态方法所占用的内存开销,在Swoole中不会自动释放,需要手动unset
销毁,这点需要注意,v1.2.10
版本起,SW-X已经强制控制器的方法不再支持静态方法定义。
但若开发者自行封装处业务类时,也要谨慎使用静态属性定义。
2、还有global
全局变量声明,在Swoole中服务可分为进程级、请求级,进程级对应Worker
进程,请求级则对应其下的子线程
,若在业务代码中声明变量为global
,则该变量会从请求级升级为进程级变量,届时变量将无法自动销毁,因为Swoole会在请求结束自动释放其子线程所占用的内存开销,但不会销毁Worker
进程相关的内存占用。
3、不要在代码中执行sleep
以及其他睡眠函数,这样会导致整个Worker进程阻塞
4、不要在代码中执行exit\die
,这样会导致整个Worker进程死亡
开启服务
v1.1.1
版本之后,SW-X的服务维护,全部改为依赖sw-x
文件,我们无需关心该文件下的代码,常用操作指令如下:
查看服务指令支持:
php sw-x
支持以下指令:
start [服务类型],以DeBug模式开启服务,此时服务不会以Daemon形式运行
start [服务类型] -d,以Daemon模式开启服务
status,查看服务器的状态
stop,停止服务器
reload,热加载所有业务代码
test [服务类型] [路由地址],单元测试执行命令,1.2.17版本起支持
controller [服务类型] [路由地址] [方法名称] [路由名称],控制器初始化自动生成命令,1.2.21版本起支持
monitor start,创建HTTP监控所需要的WEB控制台组件,1.2.22版本起支持
rpc start,创建RPC-WEB控制台所需要的WEB控制台组件,1.2.25版本起支持
在启动服务之前,我们还应该先修改对应的配置文件,配置存放在/config/服务配置.php
通过:status
输出的Memory_get_usage
和Container_count
信息,可以判断当前应用是否发生了内存溢出现象。
v1.2.2
版本后,新增Mysql_connect_count
和Redis_connect_count
可以查看到当前应用Mysql和Redis的在线连接数,数据为5秒更新一次(所有Worker进程连接池的总和)。
定时器自动载入
很多时候在实际开发中我们都需要启动一些定时任务,来处理定时任务,SW-X中提供了定时器统一加载的服务,开发者只需要将定时任务创建在/app/crontab/
目录下即可。
该目录下的定时任务,会在onStart
事件中触发载入。
从v1.2.2
版本起,不再支持自动载入定时任务,而是改为手动挂载定时任务,修改配置文件/config/crontab.php
文件。定时任务的执行方法,必须接收一个$server
参数,为当前Worker的实例。
Server服务
SW-X中的Server服务,主要是指Swoole的服务端TCP/UPD服务器;对该类基础的服务器,SW-X没有进行任何的二次处理,若是开启server服务的开发者,需要自己在/app/event/
的消息事件中实现自己的业务逻辑。
HTTP服务加载流程
控制器对象是http组件中方便客户端与服务端交互的对象,它使用了对象池对象复用模式,以及注入request
和response
对象进行数据交互
加载流程
- 用户A请求
/index
经过onRequest
消息事件转发到路由器,然后根据路由器定位到/app/controller/对应路由规则处理文件.php
控制器 - 路由器将请求对象注入到基类控制器中。
- 通过调用基类控制器的内置方法,进行页面间消息通信。
约定规范
- 项目中类名称与类文件命名,均为大驼峰,变量与类方法为小驼峰。
- (文件夹),均为小写。
- 所有控制器均要继承
\x\Controller
基类控制器。 - 在HTTP响应中,于业务逻辑代码中
echo $var
并不会将$var
内容输出至相应内容中,请调用基类控制器中的的fetch()
方法实现。
注意事项
- 注解只对路由器指向的主方法有效。
- 注解对跨控制器调用无效。
基础使用方法
sw-x中,使用基类控制器中的的$this->fetch()
方法向页面输出内容。
<?php
namespace app\controller;
use x\Controller;
class Index extends Controller
{
/**
* 向页面输出内容
* @RequestMapping(route="/", method="get", title="主页")
*/
public function index() {
return $this->fetch('Hello Word');
}
/**
* 输出视图
* @RequestMapping(route="/test")
*/
public function index() {
$this->assign('sw', 'Hello Word');
return $this->view();
}
}
HTTP服务路由
当前版本下HTTP服务的路由主要有三种模式,可以在/config/route.php
配置文件中修改。
分别为:path_info模式
、路由表模式
、兼容模式
。
path_info模式
:传统的根据Controller文件定位路由规则,例如/app/controller/index/Index.php->test()
那路由指向规则就是:/index/Index/test
路由表模式
:启用注解实现路由绑定的功能,在控制器中通过@Controller
和@RequestMapping
注解绑定路由规则。兼容模式
:就是上面两种路由规则允许同时启用。
从v1.0.2
版本,已经取消路由模式支持,改为框架强制路由模式。
Request对象
在v1.2.1
版本后,Request对象已被存储在容器中,我们只需要通过\x\Container::getInstance()->get('request')
即可全局获取到实例。
AOP操作中也不再回调Request对象,直接使用容器获取即可,但如果您需要更新Request对象信息,则注意需要重新set回容器。
Response对象
在v1.2.1
版本后,Response对象已被存储在容器中,我们只需要通过\x\Container::getInstance()->get('response')
即可全局获取到实例。
AOP操作中也不再回调Response对象,直接使用容器获取即可,但如果您需要更新Response对象信息,则注意需要重新set回容器。
关于Request
从v1.2.1
版本起,之前Controller中关于请求信息的相关函数都独立转移到\x\Request
请求类中了,相关函数的返回值没变。
获取header
使用\x\Request::header()
可以获取到请求头信息。
获取RAW数据
从v1.2.30
版本起,使用\x\Request::raw()
可以获取到RAW的请求数据。
获取GET参数
使用\x\Request::get()
可以获取到GET请求的表单信息。
获取POST参数
使用\x\Request::post()
可以获取到POST请求的表单信息。
IS_GET
使用\x\Request::is_get()
可以判断是否GET请求。
IS_POST
使用\x\Request::is_post()
可以判断是否POST请求。
IS_AJAX
从v1.2.19
版本起支持,使用\x\Request::is_ajax()
可以判断是否AJAX类型请求。
URL相关
URL相关的函数,从v1.1.5
版本起支持:
<?php
\x\Request::is_ssl(); // 是否 https
\x\Request::ip(); // 获取客户端ip
\x\Request::domain(); // 获取当前域名,不带路由和参数
\x\Request::route(); // 获取当前请求路由,不带参数
\x\Request::url(); // 获取当前URL,不带域名
\x\Request::url(true); // 获取当前URL,带域名
\x\Request::baseUrl(); // 获取当前URL,不带参数
\x\Request::baseUrl(true); // 获取当前URL,带参数
重定向
SW-X中并没有提供倒计时重定向的功能,但提供了直接重定向的支持。
使用方法很简单,举例如下:
<?php
namespace app\controller;
use x\Controller;
class Index extends Controller
{
/**
* 向页面输出内容
* @RequestMapping(route="/", method="get", title="主页")
*/
public function index() {
// 直接跳转到:当前域名/index/test路由后缀 的URL
return $this->redirect('index/test', 301);
// 带get参数跳转
// 直接跳转到:当前域名/index/test路由后缀?id=1 的URL
return $this->redirect('index/test', 301, ['id' => 1]);
// 直接用第三方域名跳转
return $this->redirect('http://sw-x.cn', 301);
}
}
视图渲染
在SW-X中,所有控制器都需要继承基础Controller,所以控制器调用视图,也是依赖基类控制器所提供的方法。
渲染模板最常用的是控制器类在继承系统控制器基类(\x\Controller)后调用display()
方法,或view()
方法。
这两个方法支持接收的参数是一样的,均为路由地址,默认为空时为当前路由地址,/
路由则对应index/index
。
display()
为直接输出解析后的html内容到客户端,
SW-X中的视图文件,统一存放在/app/view
目录下,该路径可以在/config/view.php
配置文件中进行修改,需要注意路径末尾不能带/
符号。
视图赋值
在视图文件中,除了系统常量输出无需赋值外,其他变量如果需要在模板中进行日常操作,必须先进行模板赋值,否则会抛出异常,将Controller数据传递到view层有下面2种方式:
1、assign
<?php
namespace app\controller;
use x\Controller;
class Index extends Controller
{
/**
* @RequestMapping(route="/", method="get", title="主页")
*/
public function index() {
// 赋值变量到view层
$this->assign('name', '小黄牛');
$this->assign('list', [
'id' => 1,
]);
// 输出模板
return $this->display();
}
}
2、view 或 display时直接赋值
<?php
namespace app\controller;
use x\Controller;
class Index extends Controller
{
/**
* @RequestMapping(route="/", method="get", title="主页")
*/
public function index() {
// 输出模板时直接赋值,view也一样
return $this->display('/', [
'name' => '小黄牛',
'list' => [
'id' => 1,
]
]);
}
}
关于SW-X的模板引擎
SwooleX的模板引擎支持,是参考借鉴于ThinkPHP5.1的内置模板引擎设计,在这里向ThinkPHP的研发团队致敬,小黄牛也是一个用了7年ThinkPHP的忠实粉丝。ThinkPHP5.1的模板引擎是用过最顺手的,没有之一。
变量输出
在模板中输出变量的方法很简单,例如,在控制器中先传递数据:
$this->assign('name', 'SwooleX');
return $this->display();
然后就能够在模板中使用该变量:
Insist, {$name}!
如果传输的数据是个数组:
$this->assign('data', [
'name' => 'SwooleX'
]);
return $this->display();
也支持这样解析:
Insist, {$data.name}!
使用默认值
在控制器中,我们通常会根据不同的业务逻辑,传递数据到视图中,这时候如果某个变量在一些场景中没有赋值到,这时候在模板的解析过程中就会报错。
为了应对这种情况,可以给该变量设置一个默认值:
{$user.name|default="该变量你忘了传数据啦"}
使用函数
在模板中,当开发者需要对变量使用函数时,可以这样用:
{$data.name|md5}
编译后的结果是:
<?php echo htmlentities(md5($data['name'])); ?>
其中htmlentities
方法是系统默认添加的(无需手动指定。)
如果你不需要转义(例如你需要输出html表格等内容),可以使用:
{$data.name|raw}
编译后的结果是:
<?php echo $data['name']; ?>
系统还内置了以下八个固定的过滤规则(不区分大小写)
过滤方法 | 描述 |
---|---|
date | 日期格式化(支持各种时间类型) |
format | 字符串格式化 |
upper | 转换为大写 |
lower | 转换为小写 |
first | 输出数组的第一个元素 |
last | 输出数组的最后一个元素 |
default | 默认值 |
raw | 不使用(默认)转义 |
例如
{$data.create_time|date='Y-m-d H:i'}
{$data.number|format='%02d'}
如果函数需要传递多个参数,可以这样使用:
{$data.email|substr=0,15}
编译后的结果是:
<?php echo htmlentities(substr($data['email'],0,15)); ?>
还支持多函数同时使用,多个函数之间用|
符号分隔,例如:
{$data.name|md5|sha1|substr=5,10}
编译后的结果是:
<?php echo htmlentities(substr(sha1(md5($data,name)),5,10)); ?>
多函数的调用顺序时从左到右依次执行的,而系统附加的过滤规则会在最后加上。
如果你觉得这样的调用顺序不好记忆,或者不想调用系统的过滤规则,也可以这样写:
{:substr(sha1(md5($data.name)),5,10)}
运算符
在SW-X中,也支持对变量使用常用的PHP运算符,仅支持以下八种。
运算符 | 使用示例 |
---|---|
+ | {$a+$b} |
- | {$a-$b} |
* | {$a*$b} |
/ | {$a/$b} |
% | {$a%$b} |
++ | {$a++} 或 {++$a} |
-- | {$a--} 或{--$a} |
综合运算 | {$a+$b*10+$c} |
注意,当我们使用运算符的时候,系统附加的过滤规则函数则不会再使用。
三元运算
SW-X中,也支持视图中使用三元运算符,例如:
{$data.sex ? '有年龄' : '还没出生呢'}
{$data.sex ? $info.email : $info.phone }
也支持运算判断:
{$data.sex >= 18 ? '成年啦' : '未成年'}
同时也支持PHP7的三元精简写法:
{$name ?? '没输入'}
原样输出
我通常在编辑技术的文档等相关视图的时候,都会用到跟PHP语法相关的html内容,这时候会想系统不对某一区域内的内容进行模板解析,可以使用literal
标签,例如:
{literal}
Insist, {$name}!
{/literal}
模板布局
SW-X的模板引擎内置了布局模板功能支持,可以方便的实现模板布局以及布局嵌套功能。
需要在/config/view.php
配置文件中,将layout_on
项改为true
。layout_name
为布局文件路由。
开启layout_on
后,我们的模板渲染顺序会发生变化,例如我们以为index/index
路由视图为例:
在不开启layout_on
布局模板之前,会直接渲染
app/view/index/index.html
模板文件,
开启之后,会先渲染:
app/view/layout.html
模板,布局模板的写法和其他模板的写法类似,本身也可以支持所有的模板标签以及包含文件,
区别在于有一个特定的输出替换变量{__CONTENT__}
,例如:
下面是一个典型的layout.html
模板的写法:
{include file="public/header" /}
{__CONTENT__}
{include file="public/footer" /}
经过上面布局文件的解析后,解析顺序则为:
先include
了app/view/public/header.html
文件。
再include
,app/view/index/index.html
文件,并将其最终解析的内容替换到{__CONTENT__}
关键字。
最后再include
,app/view/public/footer.html
文件。
包含文件
在上面的模板布局中,我们已经了解到SW-X在模板中是使用include
包含文件,其file
参数为视图文件对应的路由地址。
例如:
{include file="public/header" /}
则会include
进app/view/public/header.html
文件。
输出替换
在实际开发中,我们经常需要配置一些静态文件的前置路由段,例如/public/index/js/
、/public/index/css/
其中的/public/index
则为前置路由段。
为了方便维护,我们则需要使用到模板输出替换,注意:配置项严格区分大小写。
在/config/view.php
中,我们新增一个tpl_replace_string
配置项(默认情况下是不存在的)并写入以下内容:
'tpl_replace_string' => [
'__INDEX__'=>'/public/index',
]
然后我们就可以在模板中这样使用:
<script src="__INDEX__/js/jquery.min.js"></script>
循环标签
视图的循环标签支持三种:foreach
、swlist
、for
。
foreach
和swlist
为遍历数组。
for
则是创建循环递归器。
foreach标签
foreach
标签的语法与原生PHPforeach
基本一致:
{foreach $list as $key=>$value}
{$value.id} -> {$value.name}
{/foreach}
swlist标签
swlist
标签的作用与foreach
标签一致,但其语法更偏向模板引擎标签的写法:
{swlist name="list" id="value" key="key"}
{$value.id} -> {$value.name}
{/swlist}
如果你想限制只读取前20条数据,可以加入length="20"
的属性。
如果你想限制只从第10条才开始读取,可以加入offset="10"
的属性。
for标签
for
标签的语法,更偏向模板引擎标签的写法:
{for start="开始值" end="结束值" step="步进值" name="循环变量名" }
{/for}
name
的默认值是i
,step
的默认值是1
,举例如下:
{for start="1" end="100"}
{$i}
{/for}
比较标签
比较标签常用于代替一些简单的判断语句,复杂的判断条件都应用if
标签替换使用,比较标签为八个用法一致的标签组,其标签支持如下:
标签 | 含义 |
---|---|
eq或者 equal | 等于 |
neq 或者notequal | 不等于 |
gt | 大于 |
egt | 大于等于 |
lt | 小于 |
elt | 小于等于 |
heq | 恒等于 |
nheq | 不恒等于 |
例如,要求sex
变量的值大于等于18
就输出,可以使用:
{egt name="sex" value="18"}你成年啦{/egt}
条件判断
视图的条件判断标签支持两种:switch
、if
。
其语法都与原生PHP的语法相似。
switch标签
例如我们要判断订单状态:
{switch $order.status}
{case 1}已支付{/case}
{case 2}已出货{/case}
{case 3}带退货{/case}
{case 4}已退货{/case}
{default /}暂无该状态
{/switch}
同时,case
中也支持变量的传递,例如我们要判断学生的分数段
{switch $fraction}
{case $fraction > 90}是个高手啊{/case}
{case $fraction > 80}优秀{/case}
{case $fraction > 70}还行{/case}
{case $fraction > 60}刚刚好{/case}
{default /}不及格
{/switch}
if标签
if
标签基本与原生的IF一致,其用法如下,例如上面的判断学生成绩:
{if ($fraction > 90)}是个高手啊
{elseif ($fraction > 80) /}优秀
{elseif ($fraction > 70) /}还行
{elseif ($fraction > 60) /}刚刚好
{else /}不及格
{/if}
标签嵌套
SW-X中,循环标签和判断标签都均支持多重循环嵌套。
原生PHP
在实际开发中,配合循环标签等经常会用到原生得PHP代码做一些统计项等操作,这时候可以用到php
标签,其语法如下:
{php}
echo 'Insist, '.$name.'!';
{/php}
注意:{php}
标签中只能使用原生的PHP语法,不能再使用任何模板引擎的语法,例如:{$data.name}
之类的。
验证码
SW-X内置了一个图形验证码类,基于\x\Verify
类做驱动支持。
下面,我们先来看下/config/app.php
中,有关验证码的相关配置信息:
<?php
// +-----------------------------
// | 验证码设置
// +-----------------------------
'verify' => [
// 验证码字体大小(px)
'fontsize' => 20,
// 验证码图片高度
'height' => 32,
// 验证码图片宽度
'width' => 150,
// 验证码位数
'length' => 4,
// 验证码字体样式
'ttf' => '6.ttf',
// 验证码过期时间,单位:秒
'expire' => 60,
// 是否添加混淆曲线
'curve' => true,
// 是否添加杂点
'noise' => true,
// 发起验证后是否需要更新验证码
'update' => true,
],
生成验证码
$this->verify();
entry
方法用于生成验证码,同时该参数可传递3个参数,分别为:
验证码类型:可传入1、2;分别对应英数、数学运算等2种图形模式,默认为1
验证码的seesion名称:默认为__vif__
;
验证码参数:参考/config/app.php
中的verify
节点。
核验验证码
$this->verify_check(输入你看到的验证码);
check
方法用于核验验证码,同时该参数可传递2个参数,分别为:
验证码内容
验证码的seesion名称:默认为__vif__
;
文件上传
内置的上传只是上传到本地服务器,上传到远程或者第三方平台的话需要自己扩展。
假设表单代码如下:
<form action="/index/upload" enctype="multipart/form-data" method="post">
<input type="file" name="image" /> <br>
<input type="submit" value="上传" />
</form>
然后修改Index.php
控制器为如下的代码:
<?php
namespace app\controller;
use x\Controller;
class Index extends Controller
{
/**
* 输出视图
* @RequestMapping(route="/", method="get", title="主页")
*/
public function index() {
// 模板渲染
return $this->view('index');
}
/**
* @RequestMapping(route="index/upload", method="post", title="上传文件Demo")
*/
public function index() {
// 获取表单上传文件 例如上传了001.jpg
$file = $this->file('image');
// 移动到框架应用根目录/uploads/ 目录下
$info = $file->move(ROOT_PATH.'/uploads');
if($info){
// 成功上传后 获取上传信息
// 输出 保存的相对路径 /uploads/文件保存地址
echo $info->getSaveName();
// 输出 保存的文件名
echo $info->getFilename();
}else{
// 上传失败获取错误信息
echo $file->getError();
}
}
}
上传验证
支持对上传文件的验证,包括文件大小、文件类型和后缀:
<?php
namespace app\controller;
use x\Controller;
class Index extends Controller
{
/**
* 输出视图
* @RequestMapping(route="/", method="get", title="主页")
*/
public function index() {
// 模板渲染
return $this->view('index');
}
/**
* @RequestMapping(route="index/upload", method="post", title="上传文件Demo")
*/
public function index() {
// 获取表单上传文件 例如上传了001.jpg
$file = $this->file('image');
// 移动到框架应用根目录/uploads/ 目录下
$info = $file->validate(['size'=>15678,'ext'=>'jpg,png,gif'])->move(ROOT_PATH.'/uploads');
if($info){
// 成功上传后 获取上传信息
// 输出 保存的相对路径 /uploads/文件保存地址
echo $info->getSaveName();
// 输出 保存的文件名
echo $info->getFilename();
}else{
// 上传失败获取错误信息
echo $file->getError();
}
}
}
如果上传文件验证不通过,则move
方法返回false。
验证参数 | 说明 |
---|---|
size | 上传文件的最大字节 |
ext | 文件后缀,多个用逗号分割或者数组 |
type | 文件MIME类型,多个用逗号分割或者数组 |
上传配置
SW-X对文件上传有默认配置,存放在/config/app.php
的file
节点下。
<?php
// +-----------------------------
// | 文件上传配置
// +-----------------------------
'file' => [
// 最大上传大小(KB)
'size' => 15678,
// 允许上传路径
'ext' => 'jpg,jpeg,png,gif',
// 保存目录不存在是否自动创建
'auto_save' => true,
// 文件名生成算法,支持sha1,md5,time三种
'name_algorithm' => 'time',
// 文件默认保存目录
'path' => ROOT_PATH.'/upload/',
],
系统默认提供了几种上传命名规则,包括:
规则 | 描述 |
---|---|
date | 根据日期和随机数生成 |
md5 | 对文件使用md5(time规则)散列生成 |
sha1 | 对文件使用sha1(time规则)散列生成 |
上传文件组件暂不支持多文件上传功能,但可以对$this->file($FILES['pic'][0])
的方式实现循环上传。
注意:在Swoole中默认文件最大上传限制只有2M
,我们除了修改框架的文件上传限制外,还需要修改server.php
配置文件中的package_max_length
选项,该参数在v1.2.11
版本后才加入。
HTTP请求监控组件
从v1.2.22
版本起支持,在/config/server.php
配置文件中,通过http_monitor_status
参数开启监控。
同时在配置该文件中,可以修改默认的控制台账号密码,默认为:swoolex
。
该组件会在onRequest
阶段记录每一个请求的状态,并存储在/runtime/HttpMonitor/
目录中,以天为单位进行存储。
该组件主要用于代替PHP-FPM
的status
服务,便于后期请求进程BUG排查,不建议生产环境中长时间开启,健康的服务中不建议开启该组件。
注意:HTTP监控的WEB组件,需要通过sw-x monitor start
CMD命令进行创建。
WebSocket服务使用规范
在SW-X中,默认是使用官方提供的json
数据交互模式。然后会根据请求的json报文中的action
字段作为请求路由,转发到/app/socket/
目录下。
/app/socket/
就相当于HTTP的/app/controller/
为控制器层。
该目录下的所有socket控制器类均都需要继承于\x\WebSocekt
基类。
命名和使用规范一致参考HTTP的Controller
规范约定。
WSS支持
SW-X中开启WSS很简单,只需要修改/config/websocket.php
配置项中的,ssl_cert_file
、ssl_key_file
证书路径即可。
自定义请求处理
很多时候官方预设的JSON通信方式并不一定适合所有开发者,所以SW-X支持自定义自己的通信方式,只需要修改/config/websocket.php
配置中的is_onMessage
为false
即可。
这样就表示框架不再监听onMessage
事件,改由自己监听/app/event/
下的onMessage
事件,进而由自己实现数据分包。
官方请求处理
当/config/websocket.php
配置中的is_onMessage
为true
时【默认true】,则启用框架对onMessage
事件进行监听。
SW-X只支持JSON格式的数据包提交,支持启用AES
数据加解密,只需要修改/config/websocket.php
中的配置项即可。
onMessage
事件中接收到的数据格式为:
{
"action":"请求路由",
"data":请求数据
}
事件控制器
WebSocekt的控制器会根据action
字段进行路由匹配,最终找到/app/socket/
目录下的控制器文件进行处理。
改目录下的事件控制器,都需要继承\x\WebSocekt
基类。
<?php
namespace app\socket;
use x\WebSocket;
/**
* @Controller(prefix="test")
*/
class Index extends WebSocket
{
/**
* @RequestMapping(route="/index", title="action为test/index访问这里")
*/
public function index() {
return $this->fetch(200, '描述', []);
}
/**
* @RequestMapping(route="/demo", title="action为test/demo访问这里")
*/
public function demo() {
return $this->fetch(301, '描述');
}
}
获取请求参数
从v1.2.2
版本起,支持使用$this->param()
方法接收json数据包中的data
参数,如果启用AES加密传输,则会自动解密后返回。
输出返回值
WebSocket控制器中输出返回值跟HTTP控制器一样,都是调用fetch()
方法,只不过传入的参数格式不一样。
return $this->fetch(状态值, 描述, 返回值[默认是空数组])
最终的返回格式如下:
{
"action":"状态值",
"msg":"描述",
"data":"返回值",
}
关于RPC微服务
SW-X的RPC微服务主要实现了TCP的跨应用调用请求。
客户端的节点获取使用简单的评分制,平均获取可用的服务节点,当定时轮询ping到无法使用的节点时,会通过生命周期进行相关消息通知。
同时,同一个服务可以配置无数个节点,方便应用负载。
从v1.2.26
版本起,原单机服务存储已改为多机服务配置统一存储在redis中。
数据加密传输
RPC的数据传输默认开启了AES
加密方式,若需要明文传输即可,则在/config/rpc.php
配置中关闭即可。
注意:当客户端或服务端开启或关闭加密方式时,另一端必须保持参数一致。
启动服务
RPC服务端启动服务很简单,在/config/server.php
配置中修改自己想要的端口号,然后在cmd中执行php sw-x start rpc
即可启动服务。
服务存放
RPC服务端的逻辑代码,均需要统一存放在/app/rpc/
目录中,并且无需继承任何基类。
同时注意:
1、每个被允许访问的rpc操作方法,都必须时public类型,同时不能为static
2、不能定义名为$headers、$param的成员属性变量,因为该变量已被系统赋值占用
3、rpc操作方法的返回值,既为客户端所获得的返回值。
请求参数获取
RPC的请求参数获取可以直接通过$this->param
直接获取。
请求头获取
RPC的请求头获取可以直接通过$this->headers
直接获取。
返回值
RPC的返回值为操作方法的return
内容,例如下面的案例,返回了一个数组,那么客户端获取到的也将会是这个数组。
namespace app\rpc\order;
class create {
// rpc - order/create->run()服务
public function run() {
// 请求参数
var_dump($this->param);
// 请求头
var_dump($this->headers);
// 返回值
return ['id' => 1];
}
}
标记该次请求处理异常
很多时候,在实际开发中,RPC服务端处理任务异常,例如发送短信请求失败,第三方接口返回sign签名错误之类的,RPC应该return false
。
但这样我们又没办法知道,这一次的请求到达出现了什么错误,因为PHP代码并没有报错呀。
为了应对这种情况,从v1.2.31
版本起,SW-X加入了RPC服务端主动标记处理异常的支持。我们只需要在服务中执行一句代码:$this->rpc_error = true;
即可。
当该逻辑被触发时,这一次请求的所有环境因素都会被记录到RPC-WEB服务中心
内。
具体例子如下:
namespace app\rpc\order;
class create {
// rpc - order/create->run()服务
public function run() {
// 假设这是短信接口的返回
$sms = $httpClient->http()->post();
if ($sms['status'] == 'success') {
// 记录该次请求为异常请求
$this->rpc_error = true;
}
// 请求参数
var_dump($this->param);
// 请求头
var_dump($this->headers);
// 返回值
return ['id' => 1];
}
}
请求参数配置
RPC客户端的相关配置参数在/config/rpc.php
文件中进行修改。
客户端请求发包,是使用了Swoole的\Swoole\Coroutine\Client
组件。
注意:
从v1.2.26
版本起,RPC客户端在使用之前,需要先开启一个HTTP类型的RPC服务中心,当然你也可以把其中一个客户端,当作RPC的服务中,
修改/config/rpc.php
文件中的http_rpc_is
项,设置为true
即可。
严格来说,服务中心只允许存在一个,但并不强制限制。
服务初始化
客户端的初始化服务配置在/rpc/map.php
文件中进行管理。
配置格式如下:
// 路由名称
'order/create' => [
// 操作方法
'run' => [
// 多个连接池
[
'title' => '30机器', // 节点名称
'ip' => '127.0.0.1', // 节点IP
'port' => '9502', // 节点端口
'status' => 0, // 状态 0.开启(默认) 2.关闭
]
]
],
轮询延迟检测
RPC客户端的延时检测,是按照轮询3秒一次的shell ping
命令进行IP检测,若ping不通,则标记该节点不可用。
若ping通过,则记录当前延时,并修改该节点评分值。
服务评分
RPC客户端的评分是根据ping的多次延时浮动来进行加减管理,评分越高的节点会被优先使用。
服务获取
RPC客户端的节点获取,是依靠评分值、当前节点请求数、当前节点延时数来进行排序选取的。
更新某条配置
RPC客户端除了在/rpc/map.php
配置文件中设置节点外,还支持动态设置某个节点。
调用方法为:
\x\Rpc::run()->setOne(请求类名, 请求方法, 节点参数);
当节点存在时为修改,节点不存在时为新增,例子如下:
\x\Rpc::run()->setOne('order/create', 'run', [
'title' => '30机器', // 节点名称
'ip' => '127.0.0.1', // 节点IP
'port' => '9502', // 节点端口
]);
删除某条配置
RPC客户端也支持动态删除某条节点,
调用方法为:
\x\Rpc::run()->deleteOne(请求类, 请求方法, 节点名称);
当节点名称为空时,则代表删除该请求方法下的所有节点,例子如下:
\x\Rpc::run()->deleteOne('order/create', 'run', '30机器');
调用服务
RPC客户端调用代码,主要依赖\x\RpcClient
框架核心类。
使用:RpcClient->run()
方法发送RPC请求。
参数支持如下:
RpcClient->run(请求类,请求方法, 请求参数=[], 请求头=[], 失败时最大使用多少个节点(默认1个), 是否投递异步任务池(默认false), 异步任务的回调通知网址(默认false), 回调通知的请求类型(默认post))
成功返回RPC内容,失败返回false
,例子如下:
$Rpc = new \x\RpcClient();
$body = $Rpc->run('order/create', 'run', [
'id' => 1,
'name' => '小黄牛会员'
]);
注意,当最后一个参数,既异步任务投递为true
时,RpcClient当投递任务成功时,会即可返回结果true
,但并不代表任务就会执行成功,任务的成功执行需要Rpc服务端业务层自行保证实现。
异步通知数据格式如下:
array(3) {
["code"]=>
string(3) "200"
["msg"]=>
string(18) "rpc finish success"
["data"]=>
array(0) {
}
}
请求超时设置
有时候当我们的RPC节点很多时,若多个节点均被攻击或请求过高,无法请求成功时,系统是会自动轮询下一个节点,直到其中一个节点请求成功时,再返回请求结果的。这样就会导致客户端可能被堵塞等待很长时间。
为了应对这种情况,SW-X的RPC服务支持配置每个RPC请求的最大超时时间,单位为(s)秒。
全局的配置参数在/config/rpc.php
配置文件中,默认为30
秒。
若需要配置单个请求,可以使用RpcClient->set('out_time', 10)
方法。
判断是否请求成功
有时候RPC服务只返回bool布尔值,这时候我们单纯的通过if run()
结果,是没办法正确判断是否真的请求成功。
为了应对这种请求,SW-X提供了一个RpcClient->isSuccess()
方法,用于判断是否真正请求成功。
使用例子如下:
$Rpc = new \x\RpcClient();
$body = $Rpc->run('order/create', 'run', [
'id' => 1,
'name' => '小黄牛会员'
]);
if ($Rpc->isSuccess()) {
var_dump($body);
} else {
var_dump('no~!');
}
获取错误日志
RPC客户端请求失败,run()
方法只会返回false
,要获取错误日志内容,需要通过RpcClient->getMsg()
方法获取。
获取错误状态码
RPC客户端请求失败的原因有很多种,我们除了通过RpcClient->getStatus()
方法查看错误内容外,还可以通过RpcClient->getStatus()
方法查看错误状态码,若请求成功,状态码则为200
。
RPC-WEB控制台组件
从v1.2.25
版本起支持,在/config/rpc.php
配置文件中,修改默认的控制台账号密码,默认为:swoolex
。
该组件可以直接查看所有RPC服务的即时状态,并支持直接关闭开启某个服务节点。
注意:HTTP监控的WEB组件,需要通过sw-x rpc start
CMD命令进行创建。
注意:
从v1.2.26
版本起,WEB控制台已被改为RPC的统一服务中心,可以对配置进行日常的CURD,登陆凭证也改为记录Session。服务中心内修改RPC节点,会直接同步到/rpc/map.php
初始化服务配置文件中。
容器的设计原理
容器是v1.2.1
版本后才接入的框架核心,SW-X的容器实现是双容器模式,分为进程级和请求级容器,进程级容器为Worker容器,用于存储框架核心的对象实例,开发者无需关心进程级容器的调用和维护。
请求级容器为应用容器,主要用于存储请求级的对象实例,例如我们在控制器中使用容器set了一个对象,那么get出来的,也只是这个请求对应的set对象,不会获取到别的请求实例,实现了真正的请求隔离。
同时,在Swoole中静态类不会自动释放内存空间,如果开发者自定义静态类就需要非常小心的维护其内存栈,这时候就应该将静态类的实例存储在容器中,在请求结束时,容器会自动销毁该请求持有的所有容器对象。
has
容器的调用,基于框架\x\Container::getInstance()
实例,其实现为单例模式。
调用has()
方法,可以检测该对象是否存储在容器中。成功返回true
,失败返回false
。
set
容器的调用,基于框架\x\Container::getInstance()
实例,其实现为单例模式。
调用set()
方法,写入一个value
到容器中,value
可以是一个闭包或者实例。该方法没有返回值。
调用方法如下:
<?php
\x\Container::getInstance()->set(KEY, VALUE);
// KEY 为容器标识符,唯一
// VALUE 为闭包或对象实例
get
容器的调用,基于框架\x\Container::getInstance()
实例,其实现为单例模式。
调用get()
方法,获得一个容器存储实例。成功返回存储内容,失败返回false
。
调用方法如下:
<?php
\x\Container::getInstance()->get(KEY);
// KEY 为容器标识符,唯一
delete
容器的调用,基于框架\x\Container::getInstance()
实例,其实现为单例模式。
调用delete()
方法,立即删除一个容器存储实例。该方法没有返回值。
调用方法如下:
<?php
\x\Container::getInstance()->delete(KEY);
// KEY 为容器标识符,唯一
注解实现方式
SW-X的注解主要依赖反射类实现,通过扫描HTTP/SOCKET控制器目录解析整个应用的注解项并常驻内存中。
注解支持范围
由于注解是基于路由表实现的依赖注入,所以注解只对路由加载的主控制器方法有效,当再主方法内调用其他成员方法,又或者跨控制器调用它类方法时,依旧只有主控制器的注解有效。
下面我们用一个HTTP的控制器来讲解下:
<?php
namespace app\controller;
use x\Controller;
class Index extends Controller
{
/**
* 输出视图
* @RequestMapping(route="/", method="get", title="主页")
*/
public function index() {
$Db = $this->test();
}
/**
* @Ioc(class="\x\Db", name="Db")
* @RequestMapping(route="/test", method="post", title="测试获取")
*/
public function test() {
var_dump($this->Db);
return $this->Db;
}
}
上面的方法,在我们访问/test
路由的时候,Db
属性是注入成功的,但当我们访问/
路由的时候就会发现,test()
方法是错误的,因为注解没有被继承,所以Ioc
实际上并没有执行。
SW-X的注解,只对被路由器载入的控制器主方法有效。
当然,调用流程上,主方法挂载的注解,其流程上调用到的后续方法都可以使用。
注解规范
SW-X中使用注解注入需要极强的规范要求,否则注解会解析失败,具体要求如下:
- 1、注解必须在
/** */
注释体内所包含 - 2、一行注释为一条注解
- 3、只对框架内置的注解元有效
- 4、每条注解中的属性参数,都必须使用双引号做标记,例如:
@Ioc(class="依赖的类", name="注入的成员属性名称")
- 5、基本所有注解都是
属性名称="",
的方式传递参数,都是强制""
双引号,后面接入一个,
英文逗号
SW-X中支持的所有注解元如下,也可以参考这个作为注解的使用规范:
/**
* @TestCase(class="\testcase\index\test1", title="用例一")
* @Ioc(class="\x\Db", function="name('user')", name="Db")
* @AopBefore(class="app\aop\Demo", function="before")
* @AopAfter(class="app\aop\Demo", function="after")
* @AopAround(class="app\aop\Demo", function="around")
* @AopThrows(class="app\aop\Demo", function="throws")
* @Param(name="id", type="int|string", value="1", empty="true", min="10", max="20", chinese="true", callback="\lifecycle\annotate_param")
* @RequestMapping(route="/index", method="GET|POST", title="路由描述")
* @Controller(prefix="user")
* @onRoute
*/
每个注解元的解释如下:
TestCase
:单元测试绑定Ioc
:属性注入AopBefore
:AOP前置操作AopAfter
:AOP后置操作AopAround
:AOP环绕操作AopThrows
:AOP异常转发RequestMapping
:方法对应的路由绑定Controller
:控制器的前置路由绑定onRoute
:申明某个方法不能被路由访问Param
:对GET或POST参数进行前置校验
Ioc注入
主要依赖 * @Ioc()
注解元实现,主要作用是对某个方法进行前置的成员属性注入。主要参数有三个:
class
:类文件的命名空间地址function
:需要调用的class类对应的方法,该参数可为空。name
:需要注入的成员属性名称,注意:如果被注入的方法是静态的,那么请先在类的内部定义好该成员属性,并且只能为public权限,否则将注入失败。
具体使用案例如下:
<?php
namespace app\controller;
use x\Controller;
class Index extends Controller
{
/**
* Ioc注入可以多个同时使用
* @Ioc(class="\x\Db", name="Db")
* @Ioc(class="\x\Redis", name="Redis")
* @Ioc(class="\x\Db", function="name('user')", name="User")
*/
public function index() {
var_dump($this->Db);
var_dump($this->Redis);
}
}
Aop绑定
主要依赖 * @Aop*()
注解元实现,主要作用是实现AOP切面注入行为。该注解元主要参数有2个:
class
:类文件的命名空间地址function
:指定接收的class类对应的方法,该参数为空的时候默认为【run】。
Aop主要支持以下4个注解元:
AopBefore 前置
AopAfter 后置
AopAround 环绕
AopThrows 异常处理
具体使用案例如下:
<?php
namespace app\controller;
use x\Controller;
class Index extends Controller
{
/**
* 注意:同一类的Aop操作,只会生效一个
* @AopBefore(class="app\aop\Demo", function="before")
* @AopAfter(class="app\aop\Demo", function="after")
* @AopAround(class="app\aop\Demo", function="around")
* @AopThrows(class="app\aop\Demo", function="throws")
*/
public function index() {
return $this->fetch('AOP注入测试');
}
}
同时我们需要注意,对应的function都应该接收两种AOP定义的不同属性,例如上面实例中所提到的app\aop\Demo
类:
<?php
namespace app\aop;
class Demo
{
//aop 除了异常通知,其余AOP事件都需要return true程序才会向下执行,否则会抛出异常
//aop 都需要接收以下参数格式
// 前置
public function before() {
return true;
}
// 后置
public function after() {
return true;
}
// 环绕
public function around() {
return true;
}
// 异常通知
public function throws($error) {
}
}
SW-X建议:所有的AOP类都应该统一存放在/app/aop/
目录下,当然这不是强制的。
注意:同一类的Aop操作,只会生效一个。例如,同时标注两个AopBefore
,只有最后一个会生效。
路由绑定
注解元中关于路由的操作比较多,主要有以下三类:
RequestMapping 控制器对应方法使用的注解元,用于绑定路由
Controller 控制器类使用的全局注解元,用于绑定该类下的全局路由前缀
onRoute 控制器对应方法使用的注解元,用于声明该方法不允许通过路由访问
RequestMapping
注解元主要有3个参数:
route
:路由规则method
:允许的请求类型,默认为空表示不限制,多个类型允许用|
号间隔,例如:method="GET|POST"
。title
:路由描述,允许为空
Controller
注解元只有1个参数:
prefix
:路由规则
onRoute
注解元没有参数,所以在使用的时候我们不需要在后面带()
符号,只需要这样使用:@onRoute
即可
使用案例如下:
<?php
namespace app\controller;
use x\Controller;
/**
* @Controller(prefix="index")
*/
class Index extends Controller
{
/**
* @RequestMapping(route="/test", method="GET|POST", title="我是测试路由")
*/
public function index() {
return $this->fetch('我的路由是:index/test');
}
/**
* @RequestMapping(route="/demo")
* @onRoute
*/
public function demo() {
return $this->fetch('虽然我定义了路由,但用index/demo,你并不能访问我');
}
}
参数过滤
#Param
注解从v1.1.4
版本起支持。
用于对请求参数的过滤,分别支持参数传递如下:
<?php
name:参数名称
method:注解启用类型,支持GET或POST输入,只对HTTP请求有效,当输入该参数时,只有请求类型相符该注解才会生效,默认为空直接生效(v1.2.23版本起支持)
type:参数类型,多个支持使用|分隔
value:为空是对参数的预设值
empty:是否不为空,默认false,传入true既开启判断
min:最小长度
max:最大长度
chinese:判断长度是否使用mb_strlen,默认false,传入true既使用
tips:当过滤不通过时,输出的提示内容,不传入则使用系统编译提示
regular:正则表达式
callback:当过滤不通过时,系统调用的回调处理类,或函数。不填时,默认使用系统生命周期处理:\lifecycle\annotate_param->run();
注意:
1、PHP中对参数是弱类型处理,例如POST(int)1
,PHP中接收到的参数即为:(string)1
,所以在type
中需要小心使用int
类型判断,否则很容易造成is_int
函数不通过。
2、callback
支持自定义处理类,直接传入类的命名空间地址即可,系统会默认调用该类的run()
方法,也可以输入函数名称。具体的参数接收,跟处理过程,可以参考\lifecycle\annotate_param->run()
类。
一个完整的参数过滤注解大概如下:
<?php
/**
* @RequestMapping(route="param", method="get", title="测试param注解")
* @Param(name="id", method="POST", type="int|string", value="1", empty="true", min="10", max="20", chinese="true", callback="\lifecycle\annotate_param")
*/
public function param() {
return $this->fetch(null);
}
可以解释为:
HTTP请求类型为POST
请求时该注解生效,或者WebSocket请求时生效,
param参数id
,不允许为空
不存在或为null
时,默认为1
只允许int
或string
类型
最小长度10
最大长度20
不使用mb_strlen
获取字节长度
以上参数不通过时,使用\lifecycle\annotate_param
类处理回调。
什么是单元测试
单元测试注解支持,从v1.2.17
版本起支持。
主要实现,通过使用$Db->test()
方法标记声明Db语句,同时在注解中通过TestCase
注解元,绑定对应的测试用例,再通过CMD命令行,使用php sw-x test [服务类型] [路由地址]
的方式,发起单元测试调试。
标记声明Db语句,在单元测试时可防止DAO污染,达到了数据隔离的效果。
同时,同个路由地址,可以绑定多个TestCase
单元测试,会按绑定顺序依次执行。
单元测试的当前支持
1、当前单元测试注解,只支持http
服务,暂不支持websocket
、server
服务。
2、单元测试注解,只对action
注解生效,对全局controller
注解无效。
注意事项
主要依赖 * @TestCase()
注解元实现,作用是实现单元测试,防止数据库DAO污染隔离。主要参数有2个:
class
:用例类文件的命名空间地址title
:可不声明,用于简单说明该用例的场景。
1、官方建议,但不强制要求,@TestCase()
的class
类文件,都应该统一存放在/testcase/
目录下。
2、所有绑定了单元测试的操作方法,如果都含有Db操作,其Db语句,都应该使用test()
方法,申明数据隔离标识,防止DAO被测试污染。
命名规范
1、建议测试文件,应该存放在/testcase/
目录下。
2、所有测试文件,都应该继承至\x\doc\lable\TestBasics
抽象基类,并实现public function getData() : array{}
和public function getHeaders() : array{}
方法。
3、当申明了Db数据隔离标识时,测试文件中其对应的数据申明,应该以成员变量的方式进行存储,访问权限只能为public
。
案例DEMO
下面提供一个完整的测试demo:
1、HTTP控制器文件\app\controller\Index.php
:
<?php
namespace app\controller;
use x\Controller;
/**
* @Controller(prefix="")
*/
class Index extends Controller
{
/**
* @TestCase(class="\testcase\index\test", title="用例一")
* @TestCase(class="\testcase\index\test", title="用例二")
* @TestCase(class="\testcase\index\test", title="用例三")
* @RequestMapping(route="/testcase", method="get", title="单元测试注解demo")
* @Ioc(class="\x\Db", name="Db")
*/
public function testcase() {
$list = $this->Db->name('admin')->test('A1')->find();
$this->Db->return();
if ($list['name'] == '1') {
return $this->fetch('使用测试用例');
} else {
return $this->fetch($list['name']);
}
}
}
2、单元测试文件\testcase\index\test.php
:
<?php
namespace testcase\index;
// 必须继承至单元测试抽象类
use \x\doc\lable\TestBasics;
class test extends TestBasics
{
/**
* A1-数据库DB
*/
public $A1 = [
'name' => '1',
];
// 返回请求数据结构
public function getData() : array
{
return [];
}
// 返回请求头
public function getHeaders() : array
{
return [];
}
}
这时候,我们在CMD命令行界面,输入php sw-x test http /testcase
就能查看到对应的测试结果。
效果如下图:
而如果我们直接在浏览器中访问这个路由,则不受影响。
什么是自定义注解
注解机制支持自定义实现,从v1.2.16
版本起支持。
主要用于支持开发者自定义实现除系统内置注解元以外的,任意自定义注解标签,在新的源码包中实现了一个Test
注解标签案例,可参考查看。
使用场景
可能有人会说,官方已经实现了这么丰富的注解标签支持,为什么还要浪费性能实现自定义注解呢?
下面我们来看个场景:
项目A需要实现一个复杂的前置操作挂载,中间涉及了1,2,3种鉴权流程,A接口权限高3种都要挂,B接口只需要挂后两种,这时候原系统内置的AopBefore
前置注解就没办法很好的实现了。
而自定义注解的实现,就能很自由多变的应对这类场景。
注意事项
1、自定义的注解标签都应该统一存放在/annotation/
目录下;
2、自定义的注解标签均可以多次声明;
3、同个操作方法中多次声明同一个自定义的注解标签时,系统只会回调一次,但会携带多个标签参数,按声明顺序组合成list
传入。
命名规范
1、所有自定义的注解标签,都需要统一继承至\x\doc\lable\Basics
注解基类;
2、并统一实现public function run($route, $type){}
接口,用于注解回调处理;
3、当注解逻辑处理不通过时,应对调用return $this->route_error(自定义说明);
方法,用于中断后续流程;该方法最终会除非框架的route_error
生命周期处理;
4、当逻辑处理通过时,应对调用return $this->_return();
方法,用于告知系统继续向下执行;
5、假设,当我们声明一个注解标签为@TestCase
注解元时,/annotation/
下对应的文件名(类名)应该为TestCase.php
。
案例DEMO
下面我们就来看下系统自带的案例注解标签@Test
,是如何实现的吧。
1、首先,我们先在/annotation/
目录下,声明一个Test.php
文件,并写入以下代码:
<?php
namespace annotation;
use \x\doc\lable\Basics;
class Test extends Basics
{
/**
* 启动项
* @todo 无
* @author 小黄牛
* @version v1.2.10 + 2020.07.30
* @deprecated 暂不启用
* @global 无
* @param array $route 路由参数
* @param type int 路由类型 1.控制器注解 2.操作方法注解
* @return bool 返回true表示继续向下执行
*/
public function run($route, $type){
// $route是多维数组
// 当同一注释中,多次声明同一个注解时,只会回调一次,多次参数分别存放在该数组中
var_dump($route);
var_dump($type);
// return route_error函数抛出自定义错误异常
return $this->route_error('Msg内容自己随便写啦');
// 若注解通过,应该调用_return()函数,代替return true;
return $this->_return();
}
}
2、然后,我们就可以选择一个控制器类,进行注解元挂载测试了:
<?php
namespace app\controller;
use x\Controller;
/**
* @Test(msg="我是自定义的注解1")
* @Test(msg="我是自定义的注解2")
* @Test(msg="我是自定义的注解3")
* @Controller(prefix="")
*/
class Index extends Controller
{
/**
* @Test(msg="我是自定义的注解1")
* @Test(msg="我是自定义的注解2")
* @Test(msg="我是自定义的注解3")
* @RequestMapping(route="/", method="get", title="主页")
*/
public function index() {
return $this->fetch('我是主页');
}
}
重启服务之后,我们访问该路由地址,就能在XShell里查看对应的注解参数,在浏览器中可以查看到生命周期回调的处理信息。
支持范围
SW-X的路由只对HTTP
、WebSocket
服务有效,并且如果是WebSocket
服务,则必须启用框架处理模式。
路由模式
SW-X的路由模式是共用的,也就是说,不管是HTTP
还是WebSocket
服务,都是可以公用框架的路由配置,具体看:HTTP服务路由篇
请求类型过滤
在路由注解元中我们通过method
参数可以前置限制路由的请求类型,若请求不符合,则会被框架调用404重定向逻辑,具体配置404参考:自定义404篇
获取全站路由表
可能会有开发者有疑问,如果注解中使用了很多路由规则,那我要怎么知道都有哪些路由呢?为了应对这个问题,SW-X提供了获取应用全路由表的方法。
具体使用方法如下:
$array = \x\route\Table::route(); // 获取全部路由
$http = $array['http'];
$websokcet = $array['websokcet'];
最终返回的数组结构是个多维数组,下面是可能存在的节点:
[
'路由地址' => [
'n' => 命名空间地址,
'name' => '方法名称',
'method' => '请求类型',
'title' => '路由描述',
'father' => 父class的注解
'own' => function本身的注解
]
]
设置请求地址
SW-X从v1.2.11
版本起,支持HTTP客户端,用于代替PHP-FPM的CURL模块。
组件的依赖命名空间为:\x\Client
,调用http()
方法建立HTTP客户端实例。
示例:
<?php
$httpClient = new \x\Client();
$httpClient->http();
建立HTTP客户端实例后,调用domain()
方法,传入请求地址,该方法返回当前实例,可进行链式操作。
<?php
$httpClient = (new \x\Client())->http();
$httpClient->domain('https://www.sw-x.cn/api.html');
设置请求体
请求中,如果需要传递请求参数,可以调用body()
方法,该方法返回当前实例,可进行链式操作。
<?php
$httpClient = new \x\Client();
$res = $httpClient->http()
->domain('https://www.sw-x.cn/api.html')
->body(['name' => 'SW-X']);
发送请求
HTTP客户端组件,支持3类请求:get
、post
、download
。成功返回请求内容。
get
用于发起GET
请求:
<?php
$httpClient = new \x\Client();
$res = $httpClient->http()
->domain('https://www.sw-x.cn/api.html')
->body(['name' => 'SW-X'])
->get();
post
用于发起POST
请求:
<?php
$httpClient = new \x\Client();
$res = $httpClient->http()
->domain('https://www.sw-x.cn/api.html')
->body(['name' => 'SW-X'])
->post();
download
用于下载远程文件,该方法可传入两个参数:
filename
:文件保存路径
offset
:是否覆盖文件,为0
时覆盖,默认为0
<?php
$httpClient = new \x\Client();
$res = $httpClient->http()
->domain('https://www.sw-x.cn/img/Logo.png')
->download();
获取errCode
调用errCode()
方法,获得请求的错误状态码,errCode
的值等于 Linux errno
:
<?php
$httpClient = (new \x\Client)->http();
$res = $httpClient->domain('https://www.sw-x.cn/api.html')
->body(['name' => 'SW-X'])
->post();
$errCode = $httpClient->errCode();
获取statusCode
statusCode
为请求的HTTP响应状态码,可用于判断Swoole-Client请求异常状态:
<?php
$httpClient = (new \x\Client)->http();
$res = $httpClient->domain('https://www.sw-x.cn/api.html')
->body(['name' => 'SW-X'])
->post();
$statusCode = $httpClient->statusCode();
获取返回的Headers
headers
方法,返回 HTTP
响应的头信息:
<?php
$httpClient = (new \x\Client)->http();
$res = $httpClient->domain('https://www.sw-x.cn/api.html')
->body(['name' => 'SW-X'])
->post();
$headers = $httpClient->headers();
获取返回的Cookies
cookies
方法,返回 HTTP
响应的 cookie
内容:
<?php
$httpClient = (new \x\Client)->http();
$res = $httpClient->domain('https://www.sw-x.cn/api.html')
->body(['name' => 'SW-X'])
->post();
$cookies = $httpClient->cookies();
其他更多支持
HTTP客户端除了以上封装的组件支持以外,还支持原生Swoole-Client
的一些格外操作,例如setHeaders()
、setCookies()
、set()
等。
示例:
<?php
$httpClient = new \x\Client();
$res = $httpClient->http()
->domain('https://www.sw-x.cn/api.html')
->body(['name' => 'SW-X'])
->set([
'timeout' => 10,
'keep_alive' => false,
])
->setHeaders([
'User-Agent' => 'Chrome/49.0.2587.3',
'Accept' => 'text/html,application/xhtml+xml,application/xml',
])
->setCookies([
'cookie' => 'Hm_lvt_5d9f29e57619d3dab924a9fb...'
])
->post();
更多的原生方法支持,请参考Swoole官方手册:HTTP客户端篇
数据库连接池说明
SW-X中的数据库操作主要使用连接池实现,启动Swoole服务的时候会根据/config/mysql.php
中的配置项来初始化对应的PDO连接,每一次的Db请求,都会从这些连接中挑出一个来进行使用。
不过使用完成之后,记得调用return()
方法归还连接,否则将会造成连接池为空的严重BUG。
读写分离
SW-X的数据库设计是强制读写分离的,我们在/config/mysql.php
中可以看到三种不同的数据库配置,分别是:读
、写
、日志
三大类。
如果你不需要使用读写分离,则只需要填入写
的配置即可。
SW-X中调用不同的数据库连接池,是通过实例化\x\Db
类时声明的,具体如下:
$Db = \x\Db(); // 默认是调用写的连接池
$Db = \x\Db('select'); //调用读的连接池
$Db = \x\Db('log'); // 调用写日志的连接池
SW-X的数据库连接池是可以配置多个不同的数据库配置项的。
假设我们在写
的连接池中配置了3个不同IP段的Mysql连接参数,那么在服务启动的时候会对array长度进行求余,平均生成PDO连接数。并不会随机生成。
使用方式
SW-X的数据库操作主要使用ORM的链式操作,为了面向熟悉MVC框架的PHPer,SW-X兼容了95%的ThinkPHP5.1数据库操作链。
数据库归还连接
我们在使用Db
类时,实际上是从连接池中取出了一个连接,所以在我们处理完SQL业务之后,都应该调用$Db->return()
方法,将连接归还到连接中。
这是一个必须遵守的规范。
从v1.2.27
版本起,所有连接归还会在__destruct
阶段检测,未手动归还的连接会被系统自动回收。
临时切换数据库
从v1.2.10
版本起,支持临时切换数据库配置,创建一个PDO短链接实例,该连接与Mysql连接池无关,调用return
方法会释放该连接。
创建案例如下:
$Db = \x\Db([
'host' => '127.0.0.1', // 地址
'port' => '3306', // 端口
'user' => 'root', // 用户名
'password' => 'root', // 密码
'database' => 'websocket', // 库
'charset' => 'utf8mb4', // 字符集
]); // 传入配置创建临时PDO连接
name
name(表名)
:选择操作表。
SQL语句构造器,表前缀使用/config/mysql.php
配置里的,prefix
字段。
所以我们使用name()
链的时候不需要带表前缀。其使用demo如下:
$Db = new \x\Db();
$Db->name('user');
test
从v1.2.17
版本起,支持为Db操作,标记单元测试用例标识,当前启动测试调试时,获取用例对应的数据值,代替Db操作返回值,防止污染DAO。
而正常流程时,test()
声明不会影响正常流程解析。
标识字符串,只允许为英文字母开头
,遵循声明变量的命名规范。
其使用demo如下:
$Db = new \x\Db();
$Db->name('user')->test('A1');
alias
alias(别名)
:用于设置当前数据表的别名,便于使用其他的连贯操作例如join方法等。
示例:
$Db = new \x\Db();
$Db->name('user')->alias('A');
// 当然你也可以这样写
$Db->name('user AS A');
最终生成的SQL语句类似于:FROM tp_user AS A;
where
where()
:用于构造SQL执行条件,该语法实现了三种场景支持,并没有完全实现ThinkPHP5.1的where
语法。
场景一:多条件数组查询
$where = [
['id' , '<>' , 1],
['money', '>=', 100],
['name', 'like', '%小黄牛%'],
];
$Db = new \x\Db();
$Db->name('user')->where($where);
最终生成的SQL语句类似于:
FROM tp_user WHERE id <> 1 AND money >= 100 AND name like '%小黄牛%';
场景二:多条件多链查询
$where = [
['id' , '<>' , 1],
['money', '>=', 100],
['name', 'like', '%小黄牛%'],
];
$Db = new \x\Db();
$Db->name('user')->where('id' , '<>' , 1)->where('money', '>=', 100)->where($where);
最终生成的SQL语句类似于:
FROM tp_user WHERE id <> 1 AND money >= 100 AND name like '%小黄牛%';
场景三:便捷等于查询
$Db = new \x\Db();
$Db->name('user')->where('id' , 1)->where('name', '小黄牛');
最终生成的SQL语句类似于:
FROM tp_user WHERE id=1 AND name='小黄牛';
最后注意:where()
链在一条SQL语句中是可以多次使用的,其执行顺序是先进先执行,相同的语句并不会覆盖,所以使用的时候需要自己注意下。
whereOr
whereOr()
从v1.2.16
版本起开始支持。
主要用于配合where()
方法实现Sql语句的OR
条件语句构造。
whereOr()
不支持数组的方式传递参数,不支持单独使用,不支持最左使用。
使用示例:
$Db = new \x\Db();
$Db->name('user')->where('id' , 1)->whereOr('name', '小黄牛')->whereOr('name', '=', '小黄猪');
最终生成的SQL语句类似于:
FROM tp_user WHERE ((id=1) OR name="小黄牛" OR name="小黄猪");
最后注意:where()
和whereOr
链在一条SQL语句中是可以多次使用的,其执行顺序是先进先执行,相同的语句并不会覆盖,所以使用的时候需要自己注意下。
whereIn
whereIn()
从v1.2.16
版本起开始支持。
主要用于实现Sql语句的In
包含查询语句构造。
使用示例:
$Db = new \x\Db();
$Db->name('user')->where('id' , 1)->whereIn('pid', '(1,2,3,4)');
最终生成的SQL语句类似于:
FROM tp_user WHERE (id=1 AND pid IN (1,2,3,4));
whereNotIn
whereNotIn()
从v1.2.16
版本起开始支持。
主要用于实现Sql语句的Not In
不包含查询语句构造。
使用示例:
$Db = new \x\Db();
$Db->name('user')->where('id' , 1)->whereNotIn('pid', '(1,2,3,4)');
最终生成的SQL语句类似于:
FROM tp_user WHERE (id=1 AND pid NOT IN (1,2,3,4));
field
field()
:方法主要作用是标识要返回或者操作的字段,可以用于查询和写入操作。
示例:
$Db = new \x\Db();
$Db->name('user')->field('id, name');
最终生成的SQL语句类似于: id,name FROM tp_user;
注意:如果不使用field()
链执行查询
操作,默认是*
符号。
limit
limit()
:方法主要用于指定查询和操作的数量。
示例:
$Db = new \x\Db();
$Db->name('user')->limit(10, 20);
最终生成的SQL语句类似于: FROM tp_user limit 10,20;
也可以这样调用:
$Db = new \x\Db();
$Db->name('user')->limit(10);
最终生成的SQL语句类似于: FROM tp_user limit 10;
page
page()
:方法主要用于分页查询。最终生成的语法也是limit结构。
示例:
$Db = new \x\Db();
$Db->name('user')->page(1, 10);
最终生成的SQL语句类似于: FROM tp_user limit 0, 10;
之后的页数是这样:
$Db = new \x\Db();
$Db->name('user')->page(2, 10);
最终生成的SQL语句类似于: FROM tp_user limit 10, 10;
order
order()
:方法用于对操作的结果排序或者优先级限制。
示例:
$Db = new \x\Db();
$Db->name('user')->order('id DESC, money ASC');
最终生成的SQL语句类似于: FROM tp_user ORDER BY id DESC, money ASC;
having
having()
:方法用于配合group方法完成从分组的结果中筛选(通常是聚合条件)数据。
示例:
$Db = new \x\Db();
$Db->name('user')->having('count(id)>3');
最终生成的SQL语句类似于: FROM tp_user HAVING count(id)>3;
group
group()
:方法通常用于结合合计函数,根据一个或多个列对结果集进行分组 。
group()
方法只有一个参数,并且只能使用字符串。
示例:
$Db = new \x\Db();
$Db->name('user')->group('id');
最终生成的SQL语句类似于: FROM tp_user GROUP BY id;
join
join()
:方法用于根据两个或多个表中的列之间的关系,从这些表中查询数据。join通常有下面几种类型,不同类型的join操作会影响返回的数。
INNER JOIN: 左右表有匹配,则返回组合行
LEFT JOIN: 即使右表中没有匹配,也从左表返回所有的行(默认的JOIN类型)
RIGHT JOIN: 即使左表中没有匹配,也从右表返回所有的行
FULL JOIN: 只要其中一个表中存在匹配,就返回行
同时注意:join
语法中的表名不需要带表前缀,Class会自动读取配置文件中的前缀设置。
示例:
$Db = new \x\Db();
$Db->name('user')->alias('A')->join('user_data B', 'A.id=B.user_id');
最终生成的SQL语句类似于: FROM tp_user AS A LEFT JOIN tp_user_data AS B ON A.id=B.user_id;
我们也可以自己指定JOIN方式:
$Db = new \x\Db();
$Db->name('user')->alias('A')->join('user_data B', 'A.id=B.user_id', 'inner');
最终生成的SQL语句类似于: FROM tp_user AS A INNER JOIN tp_user_data AS B ON A.id=B.user_id;
注意:join
链可以多个使用,顺序是先用先执行。
select
select()
:是链式操作的终结方法之一,该链支持Db类的所有查询表达式,主要用于查询多条记录。
该方法调用后会返回最终构造成的SQL语句。
示例:
$Db = new \x\Db();
$Db->name('user')->field('id')->where('id', 1)->order('id DESC')->select();
最终生成的SQL语句类似于:SELECR id FROM tp_user where id=1 ORDER BY id DESC;
同时select()
还支持传入false
表示不执行SQL语句,只返回最终构造的SQL语句字符串。
$Db = new \x\Db();
$Db->name('user')->select(false);
更多的链式组合可以自己尝试下。
find
find()
:是链式操作的终结方法之一,该链支持上述9种查询表达式,主要用于查询一条记录。主要不支持limit
、page
链。
该方法调用后会返回最终构造成的SQL语句。
示例:
$Db = new \x\Db();
$Db->name('user')->field('id')->where('id', 1)->order('id DESC')->find();
最终生成的SQL语句类似于:SELECR id FROM tp_user where id=1 ORDER BY id DESC limit 1;
同时find()
还支持传入false
表示不执行SQL语句,只返回最终构造的SQL语句字符串。
$Db = new \x\Db();
$Db->name('user')->find(false);
更多的链式组合可以自己尝试下。
value
value()
从v1.1.7
版本起开始支持。
是链式操作的终结方法之一,该链支持上述9种查询表达式,主要用于查询一条记录的某个字段值。
主要不支持limit
、page
链。
该方法调用后会返回最终构造成的SQL语句。
示例:
$Db = new \x\Db();
$Db->name('user')->where('id', 1)->value('id');
最终生成的SQL语句类似于:SELECR id FROM tp_user where id=1 limit 1;
成功返回field
对应的值,失败返回false
delete
delete()
:是链式操作的终结方法之一,用于构造删除语句,该链只支持主要的where查询表达式,不支持:order
、page
、limit
类型的表达式。
该方法调用后会返回最终构造成的SQL语句。
从v1.2.2
版本起,若该方法没有前置where
条件,则会直接返回
$Db = new \x\Db();
$Db->name('user')->where('id', 1)->order('id DESC')->delete();
最终生成的SQL语句类似于:DELETE FROM tp_user where id=1 ORDER BY id DESC;
更多的链式组合可以自己尝试下。
update
update()
:是链式操作的终结方法之一,用于构造单条记录更新语句,该链只支持主要的where查询表达式,不支持:order
、page
、limit
类型的表达式。
该方法调用后会返回最终构造成的SQL语句。
从v1.2.2
版本起,若该方法没有前置where
条件,则会直接返回
$Db = new \x\Db();
$Db->name('user')->where('id', 1)->update(['name' => '小黄牛', 'money' => 100]);
最终生成的SQL语句类似于:UPDATE tp_user SET name='小黄牛',money=100 where id=1;
insert
insert()
:是链式操作的终结方法之一,用于构造单条 或 多条记录新增语句。
该方法调用后会返回最终构造成的SQL语句。
单条新增示例:
$Db = new \x\Db();
$Db->name('user')->insert(['name' => '小黄牛', 'money' => 100]);
最终生成的SQL语句类似于:INSERT INTO tp_user (name,money) VALUES ('小黄牛',100);
多条新增示例:
$data = [
['name' => '小蓝牛', 'money' => 50],
['name' => '小红牛', 'money' => 70],
['name' => '小黄牛', 'money' => 100],
];
$Db = new \x\Db();
$Db->name('user')->insert($data);
最终生成的SQL语句类似于:INSERT INTO tp_user (name,money) VALUES ('小蓝牛',50),('小红牛',70),('小黄牛',100);
注意:当使用批量新增时,所有的插入数据结构顺序需要与第一条数据顺序一致,否则将会出错,例如下拉语句就是错误的:
$data = [
['name' => '小蓝牛', 'money' => 50],
['money' => 70, 'name' => '小红牛'],
['money' => 100],
];
$Db = new \x\Db();
$Db->name('user')->insert($data);
insertGetId
insertGetId
方法从v1.1.10
版本起支持,起用法与insert
方法一致,但不支持批量新增。
若新增成功则返回自增主键ID,起获取主键ID的语法为:SELECT LAST_INSERT_ID() as num;
setInc
setInc()
:是链式操作的终结方法之一,用于构造自增语句,该链只支持主要的where查询表达式,不支持:order
、page
、limit
类型的表达式。
该方法调用后会返回最终构造成的SQL语句。
示例:
$Db = new \x\Db();
$Db->name('user')->where('id', 1)->setInc('money', 20);
最终生成的SQL语句类似于:UPDATE tp_user SET money=money+20 where id=1;
如果我们不填自增数,默认会是1
:
$Db = new \x\Db();
$Db->name('user')->where('id', 1)->setInc('money');
最终生成的SQL语句类似于:UPDATE tp_user SET money=money+1 where id=1;
setDec
setDec()
:是链式操作的终结方法之一,用于构造自减语句,该链只支持主要的where查询表达式,不支持:order
、page
、limit
类型的表达式。
该方法调用后会返回最终构造成的SQL语句。
示例:
$Db = new \x\Db();
$Db->name('user')->where('id', 1)->setDec('money', 20);
最终生成的SQL语句类似于:UPDATE tp_user SET money=money-20 where id=1;
如果我们不填自减数,默认会是1
:
$Db = new \x\Db();
$Db->name('user')->where('id', 1)->setDec('money');
最终生成的SQL语句类似于:UPDATE tp_user SET money=money-1 where id=1;
count
count()
从v1.1.7
版本起支持。
是链式操作的终结方法之一,用于统计数量,参数是要统计的字段名(可选)。支持基本的查询构造器。
示例:
$Db = new \x\Db();
$Db->name('user')->where('id', 1)->count();
最终生成的SQL语句类似于:SELECT COUNT(*) AS swoolex FROM `sw_user` WHERE id=1 LIMIT 1;
成功返回统计值,失败返回false
max
max()
从v1.1.7
版本起支持。
是链式操作的终结方法之一,用于获取最大值,参数是要统计的字段名(必须)。支持基本的查询构造器。
示例:
$Db = new \x\Db();
$Db->name('user')->where('id', 1)->max('score');
最终生成的SQL语句类似于:SELECT MAX(*) AS swoolex FROM `sw_user` WHERE id=1 LIMIT 1;
成功返回统计值,失败返回false
min
min()
从v1.1.7
版本起支持。
是链式操作的终结方法之一,用于获取最小值,参数是要统计的字段名(必须)。支持基本的查询构造器。
示例:
$Db = new \x\Db();
$Db->name('user')->where('id', 1)->min('score');
最终生成的SQL语句类似于:SELECT MIN(*) AS swoolex FROM `sw_user` WHERE id=1 LIMIT 1;
成功返回统计值,失败返回false
avg
avg()
从v1.1.7
版本起支持。
是链式操作的终结方法之一,用于获取平均值,参数是要统计的字段名(必须)。支持基本的查询构造器。
示例:
$Db = new \x\Db();
$Db->name('user')->where('id', 1)->avg('score');
最终生成的SQL语句类似于:SELECT AVG(*) AS swoolex FROM `sw_user` WHERE id=1 LIMIT 1;
成功返回统计值,失败返回false
sum
sum()
从v1.1.7
版本起支持。
是链式操作的终结方法之一,用于获取总分,参数是要统计的字段名(必须)。支持基本的查询构造器。
示例:
$Db = new \x\Db();
$Db->name('user')->where('id', 1)->sum('score');
最终生成的SQL语句类似于:SELECT SUM(*) AS swoolex FROM `sw_user` WHERE id=1 LIMIT 1;
成功返回统计值,失败返回false
执行原生SQL
SW-X中支持直接调用query()
与 exec()
方法执行原生的SQL语句,但其中的表名称并不能使用到配置文件中的表前缀配置项。
query()
用于执行select语句
exec()
用于执行除了select外的其他SQL语句
$Db = new \x\Db();
$Db->query('select * from tp_user;');
子查询构造器
SW-X中只推荐使用buildSql()
方法搭配table()
方法来构造子查询语句。buildSql()
方法是不执行SQL语句,返回子查询结构语句:
$Db = new \x\Db();
$Db->name('user')->field('id')->where('id', 1)->order('id DESC')->buildSql();
最终生成的SQL语句类似于:( SELECR id FROM tp_user where id=1 ORDER BY id DESC )
再配合table()
方法就能实现子查询操作:
$Db = new \x\Db();
$sql = $Db->name('user')->field('id')->where('id', 1)->order('id DESC')->buildSql();
$Db->table($sql.' AS A')->select();
生成的SQL语句为:SELECT * FROM ( SELECR id FROM tp_user where id=1 ORDER BY id DESC );
table
table()
不会自动调用配置中的表前缀,该方法一般只用于配置子查询时查询的构造语句。
参考子查询构造器。
调试SQL
当我们不想要执行SQL语句,只想查看SQL语句字符串的时候,可以使用debug()
方法,该方法是表示不执行SQL语句,并返回SQL字符串:
$Db = new \x\Db();
$Db->name('user')->field('id')->where('id', 1)->order('id DESC')->debug()->select();
// 等同于
$Db->name('user')->field('id')->where('id', 1)->order('id DESC')->select(false);
// 但debug语法一样适用于delete、insert、update、query语句
时间查询
whereTime()
方法提供了日期和时间字段的快捷查询,示例如下:
$Db = new \x\Db();
// 大于某个时间
$Db->name('user')
->whereTime('create_time', '>=', '1970-10-1')
->select();
// 小于某个时间
$Db->name('user')
->whereTime('create_time', '<', '2000-10-1')
->select();
// 时间区间查询
$Db->name('user')
->whereTime('create_time', 'between', ['1970-10-1', '2000-10-1'])
->select();
// 不在某个时间区间
$Db->name('user')
->whereTime('create_time', 'not between', ['1970-10-1', '2000-10-1'])
->select();
同时,whereTime()
方法还提供了更方便的时间表达式查询,例如::
$Db = new \x\Db();
// 获取今天的博客
$Db->name('blog')
->whereTime('create_time', 'today')
->select();
// 获取昨天的博客
$Db->name('blog')
->whereTime('create_time', 'yesterday')
->select();
// 获取本周的博客
$Db->name('blog')
->whereTime('create_time', 'week')
->select();
// 获取上周的博客
$Db->name('blog')
->whereTime('create_time', 'last week')
->select();
// 获取本月的博客
$Db->name('blog')
->whereTime('create_time', 'month')
->select();
// 获取上月的博客
$Db->name('blog')
->whereTime('create_time', 'last month')
->select();
// 获取今年的博客
$Db->name('blog')
->whereTime('create_time', 'year')
->select();
// 获取去年的博客
$Db->name('blog')
->whereTime('create_time', 'last year')
->select();
事务
$Db = new \x\Db();
// 启动事务
$Db->begin();
try {
$Db->name('user')->find();
$Db->name('user')->where('id', 1)->delete();
// 提交事务
$Db->commit();
} catch (\Exception $e) {
// 回滚事务
$Db->rollback();
}
事务隔离
在SW-X中,数据库事务不能跨连接池使用,也就是说在$Db
中,不能包含$Db2
的业务,否则会查询事务隔离级别的报错。
Model说明
Model支持从V1.1.3
版本起,其实现主要以Db实例转发实现。
Model类建议存放在/app/model
目录下,但不强制要求。
每个Model类,都必须继承\x\Model
基类。
命名规则为:表名[不带前缀]Model.php
。表名首字母大写。
注意,如果您的表名是由多个下划线_
所组成,例如:user_action_log
,那么Model名即为:UserActionLogModel.php
。
具体可以参考框架包下的示例代码:/app/model
目录下。
注意:Model中禁止使用静态方法,防止Db连接池无法在实例释放后自动释放。
关于连接归还
在ORM操作中,我们new \x\Db
后,当使用完连接池,则需要手动调用$this->Db->return()
方法归还连接。
Model类中则不需要,当实例调用结束后,会自动调用$this->return()
归还连接。
选择连接池类型
原来在new \x\Db()
时,我们可以传入参数,例如select
,调用读的连接池。
而在Model中也是一样,例如我们定义了一个/app/model/UserModel.php
类,我们也可以跟Db一样,new \app\model\UserModel('select');
即可。
配置说明
SW-X的配置架构没有系统跟应用配置之分,统一都是读取/config/
目录下的所有文件。
同时,SW-X的配置全部都是二级配置,当没有指定配置读取的时候,表示获取所有配置项。
/config/
下的文件名为一级配置项,其文件内部的数组内容,则为对应的二级配置。
如果需要自定义跟框架无关的配置,只需要在/config/
目录下创建一个新文件即可。
配置读取
\x\Config
配置类使用单例模式实现,在框架初始化的时候,会拉起全部配置并常驻内存。
\x\Config
的读取和修改都需要通过run()
先获取单例实例
例如获取app
配置项的file
参数:
<?php
\x\Config::run()->get('app.file');
// 如果还需要获取三级配置项,可以这样使用
\x\Config::run()->get('app.file.size');
get()
方法支持无限极配置读取,只需要使用.
符号间隔即可。
配置修改
配置的修改,使用set()
方法实现。
例如修改app
配置项的file
参数:
<?php
\x\Config::run()->set('app.file', [
// 最大上传大小(KB)
'size' => 15678,
// 允许上传路径
'ext' => 'jpg,jpeg,png,gif',
// 保存目录不存在是否自动创建
'auto_save' => true,
// 文件名生成算法,支持sha1,md5,time三种
'name_algorithm' => 'time',
// 文件默认保存目录
'path' => ROOT_PATH.'/upload/',
]);
// 如果只需要修改三级配置项,可以这样使用
\x\Config::run()->set('app.file.size', 15678);
set()
方法支持无限极配置修改,只需要使用.
符号间隔即可。
同时注意:由于配置项是缓存到常驻内存中的,所以配置项的修改不是局部,而是全局生效的,这点需要注意。
Redis
\x\Redis
类使用连接池实现,对应的配置在/config/redis.php
配置项中修改。
获取Redis连接池,只需要new \x\Redis();
即可,不过跟Mysql连接池一样,当使用完后,需要调用return()
归还连接。
具体使用案例如下:
<?php
// 获取连接
$redis = \x\Redis();
// 执行指令
$redis->set('name', '小黄牛');
$redis->get('name');
// 归还连接
$redis->return();
// v1.2.30版本起,支持临时更改表前缀
// 获取连接
$redis = (\x\Redis()->prefix('swoolex_'));
从v1.2.27
版本起,所有连接归还会在__destruct
阶段检测,未手动归还的连接会被系统自动回收。
Session说明
SW-X的Session实现,主要依赖Redis存储,不支持原生的PHP-SESSION,所以在使用SESSION之前,请先到/config/redis.php
配置项中开始redis支持。
Session的配置项可以在/config/app.php
配置中进行修改。
has
SESSION的操作,主要依赖\x\Session
类实现,has()
方法用于判断一个session是否存在。
<?php
if (\x\Session::has('admin')) {
echo '登陆中';
} else {
echo '已退出';
}
get
get()
方法用于读取一个session值。
<?php
$admin = \x\Session::get('admin');
set
set()
方法用于设置一个session值。
<?php
\x\Session::set('admin', '小黄牛');
// 你还可以设置自动过期时间,默认7200S
\x\Session::set('admin', '小黄牛', 3600);
delete
delete()
方法用于删除一个session。
<?php
$res = \x\Session::delete('admin');
clear
clear()
方法用于清空所有session。
<?php
$res = \x\Session::clear();
Cookies
Cookie的配置项可以在/config/app.php
配置中进行修改。
has
Cookie的操作,主要依赖\x\Cookie
类实现,has()
方法用于判断一个cookie是否存在。
<?php
if (\x\Cookie::has('admin')) {
echo '登陆中';
} else {
echo '已退出';
}
get
get()
方法用于读取一个cookie值。
<?php
$admin = \x\Cookie::get('admin');
set
set()
方法用于设置一个cookie值。
<?php
\x\Cookie::set('admin', '小黄牛');
// 你还可以设置自动过期时间,默认7200S
\x\Cookie::set('admin', '小黄牛', 3600);
delete
delete()
方法用于删除一个cookie。
<?php
$res = \x\Cookie::delete('admin');
clear
clear()
方法用于清空所有cookie。
<?php
$res = \x\Cookie::clear();
多语言
多语言的实现,依赖于\x\Lang
类,该类由单例模式设计,统一使用\x\Lang::run()->get();
的方式获取语言项。
语言包统一存放在/lang/
目录下,以文件名为语言包名称进行命名。
对于的语言包启用设置,在/config/app.php
文件中进行配置。
如果你不想修改配置文件,也可以通过传入run()
参数进行临时获取,例如:
<?php
// 读取默认语言包
\x\Lang::run()->get('hello word sw-x');
// 临时读取en语言包中的项
\x\Lang::run('en')->get('hello word sw-x');
dd
系统函数:dd()
,主要是用于打印数据类型,由于swoole中,直接var_dump
是没办法输出到页面上的,所以系统提供了这个函数由于代替var_dump
,具体用法如下:
<?php
// 假设下面是HTTP控制器中
return $this->fetch(dd([
'name' => 'SW-X',
'des' => '真帅!'
]));
系统常量
VERSION SW-X版本号
ROOT_PATH 项目根地址,末尾不带/符号
RUNTIME_PATH 日志类文件存放根地址,末尾带/符号