SW-X
SW-X 是一款基于Swoole 实现的常驻内存便捷开发式PHP框架,专为高性能、便捷开发而生,摆脱传统的FPM运行模式。 在开发上,我们为您准备了以下常用组件:
- http 服务服务器
- websocket 服务服务器
- server 服务服务器
- 数据库ORM
- 图片验证码
- 模板渲染引擎
- 协程redis连接池
- 协程mysql 连接池
- 注解路由
- 注解Ioc
- 注解Aop
以上组件为常用组件,更多组件请看组件库文档
维护团队
- 作者
- 小黄牛 1731223728@qq.com
- 团队成员
- 暂无,期待您的加入
其他
- SW-X官方一群 1012826310
-
商业支持:
- QQ 1731223728
-
作者微信
- junphper
QQ交流群
注意事项
- 不运行在代码中执行sleep类型函数,因为这样有可能导致系统堵塞,出现大量等待进程,
- 不能使用exit/die语句,因为这样会导致worker进程重启,这样的后果是其进程下挂载的连接池也会跟着重启。
- 连接池是使用完成之后,必须要return归还连接,否则将造成连接池队列被取空的问题。
约定规范
- 项目中类名称与类文件(文件夹)命名,均为大驼峰,变量与类方法为小驼峰。
- 在HTTP响应中,于业务逻辑代码中echo $var 并不会将$var内容输出至相应内容中,请调用实例中的fetch()方法实现。
SW-X版本更新记录
V1.0.3(2020年7月05)
1、修复WebSocket分包错误的BUG
2、onReceive事件,加多一个data参数
3、WebSocket删除支持Cookie与Session的错误逻辑
V1.0.2(2020年6月12)
1、废弃路由模式
2、优化了部分框架核心代码
3、从原来的服务共享事件回调模式,改成独立服务回调事件处理
V1.0.1-dev(2020年6月2)
小婴婴出生啦~
捐赠
您的捐赠是对SW-X项目开发组最大的鼓励和支持。我们会坚持开发维护下去。 您的捐赠将被用于:
- 持续和深入地开发
- 文档和社区的建设和维护
支付宝
微信
通过微信捐赠的朋友,请留言你的侠号
捐赠者列表
- *松鼠
- *猫唇
环境要求
满足基本的环境要求才能运行框架,SW-X 框架对环境的要求十分简单,只需要满足运行 Swoole4.4+ 拓展的条件,并且 PHP 版本在 7.1 以上即可。
基础运行环境
- 保证 PHP 版本大于等于 7.1
- 保证 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.0.1
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 / {
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/
。
从v1.0.2
版本开始,改为独立服务回调处理,其目录为/app/event/服务类型/
。
关于调试模式
当开启调试模式时,SW-X会实现以下几个功能:
1、将报错写入到/runtime/log/
目录下
2、将所有执行的SQL语句【除了select】都记录到/runtime/sql/
目录下
3、所有view文件都会重新解析
开启服务
在服务启动之前,我们先打开/index.php
文件,通过App
类来启动服务。
// 加载基础文件
require __DIR__ . '/swoolex/base.php';
// 下面三者只能取其中一个启动
// 启动HTTP服务
App::run()->service('http')->start();
// 启动WebSocket服务
App::run()->service('websocket')->start();
// 启动Server服务
App::run()->service('server')->start();
在启动服务之前,我们还应该先修改对应的配置文件,配置存放在/config/服务配置.php
定时器自动载入
很多时候在实际开发中我们都需要启动一些定时任务,来处理定时任务,SW-X中提供了定时器统一加载的服务,开发者只需要将定时任务创建在/app/crontab/
目录下即可。
该目录下的定时任务,会在onStart
事件中触发载入。
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对象
SW-X中,Request对象是通过对象注入的方式已经存储在Controller中,
我们只需要通过$this->request()
即可获取到实例。
也可以使用$this->header()
获取到请求头信息
Response对象
SW-X中并没有方法可以获取到Response对象,因为Controller中已经实现了Response操作的必要功能。
获取header
使用$this->header()
可以获取到请求头信息。
获取GET参数
使用$this->get()
可以获取到GET请求的表单信息。
获取POST参数
使用$this->post()
可以获取到POST请求的表单信息。
IS_GET
使用$this->is_get()
可以判断是否GET请求。
IS_POST
使用$this->is_post()
可以判断是否POST请求。
重定向
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);
}
}
自定义404
在很多时候,当我们路由器找不到规则,又或者是请求类型被拦截时,我们不希望程序卡死没有影响,而是自己定义一个404页面。
应对这类需求,开发者只需要修改/app/route.php
配置文件下的404
和error_class
参数即可。
该参数是表示将以上两种错误不抛出,而是转发到指定的处理类中。
视图实例化
视图功能由\x\View
类配合视图驱动(模板引擎)类一起完成,目前的内置模板引擎包含原生PHP语法和基础模板语法。
在控制器中,我们只需要调用$this->view()
即可实现视图输出。
SW-X的视图文件统一存放在/app/view/
目录下。
view()
的模板选择比较特殊,主要情况如下:
- 当你使用注解实现路由时,例如当前路由为:
/test
时,那么视图文件应该为:/app/view/test.html
- 当你不使用注解路由时,例如当前路由为:
/index/test
时,那么视图文件应该为:/app/view/index/test.html
- 当你自己指定视图文件时,例如
$this->view('shop/index')
时,那么视图文件应该为:/app/view/shop/index.html
- 当你的路由规则为:
/
首页时,请用上面的第三种方法,手动指定一个index
视图文件作为首页视图。
模板引擎
视图的模板文件可以支持不同的解析规则,默认情况下无需手动初始化模板引擎。
可以通过修改配置文件得到方式对模板引擎进行初始化。
视图的配置项存放在/config/app.php
中:
<?php
// +-----------------------------
// | 模板设置
// +-----------------------------
'template' => [
// 模板后缀
'view_suffix' => '.html',
// 模板引擎标签开始标记
'tpl_begin' => '{',
// 模板引擎标签结束标记
'tpl_end' => '}',
],
模板赋值
除了系统变量和配置参数输出无需赋值外,其他变量如果需要在模板中输出必须首先进行模板赋值操作,绑定数据到模板输出需要使用assign
方法:
<?php
namespace app\controller;
use x\Controller;
class Index extends Controller
{
/**
* 输出视图
* @RequestMapping(route="/", method="get", title="主页")
*/
public function index() {
// 模板变量赋值
$this->assign('name', '哈哈');
$this->assign('data', ['nice' => '小黄牛']);
// 模板渲染
return $this->view('index');
}
}
模板标签
普通标签用于变量输出,普通模板标签默认以{
和 }
作为开始和结束标识,并且在开始标记紧跟标签的定义,如果之间有空格或者换行则被视为非模板标签直接输出。
例如:{$name}
、{$vo.name}
都属于正确的标签,而{ $name}
、{ $vo.name}
则不属于。
要更改普通标签的起始标签和结束标签,可以更改下面的配置参数:
<?php
// +-----------------------------
// | 模板设置
// +-----------------------------
'template' => [
// 模板后缀
'view_suffix' => '.html',
// 模板引擎标签开始标记
'tpl_begin' => '<{',
// 模板引擎标签结束标记
'tpl_end' => '}>',
],
普通标签的定界符就被修改了,原来的 {$name}
和 {$vo.name}
必须使用 <{$name}>
和 <{$vo.name}>
才能生效了。
变量输出
在模板中输出变量的方法很简单,例如,在控制器中我们给模板变量赋值:
$this->assign('nice', '小黄牛');
$this->view('index');
然后就可以在模板中使用:
Hello,{$name}!
模板编译后的结果就是:
Hello,<?php echo $name;?>!
这样,运行的时候就会在模板中显示: Hello,小黄牛!
注意模板标签的{
和$
之间不能有任何的空格,否则标签无效。所以,下面的标签
Hello,{ $name}!
将不会正常输出name变量,而是直接保持不变输出: Hello,{ $name}!
同时,还支持一维数组变量解析:
$this->assign('data', ['nice' => '小黄牛']);
$this->view('index');
然后就可以在模板中使用:
Hello,{$data.name}!
模板编译后的结果就是:
Hello,!
系统常量输出
其语法格式为:
{:常量名称}
例如:
{:ROOT_PATH}
模板编译后的结果就是:
<?php echo ROOT_PATH;?>
原生语法
Php代码可以和标签在模板文件中混合使用,可以在模板文件里面书写任意的PHP语句代码,包括下面两种方式:
使用php标签
例如:
{php}echo 'Hello,world!';{/php}
我们建议需要使用PHP代码的时候尽量采用php标签,因为原生的PHP语法可能会存在解析错误或者冲突的可能。
使用原生php代码
<?php echo 'Hello,world!'; ?>
注意:php标签或者php代码里面就不能再使用其他模板标签了,因此下面的几种方式都是无效的:
{php}{$name}{/php}
<?php echo {$name}; ?>
循环和IF
基于模板引擎的php标签和一维数组标签,我们可以实现便捷的foreach
循环 和if
操作:
foreach循环
例如,假设我们查询数据库后,获得一个二维数组结果集:
{php} foreach ($info as $v): {/php}
昵称:{$v.nice}
账号:{$v.name}
{php} endforeach; {/php}
模板编译后的结果就是:
<?php foreach ($info as $v): ?>
昵称:<?php echo $v['nice']; ?>
账号:<?php echo $v['name']; ?>
<?php endforeach; ?>
IF语句
{php} if (is_array($info)): {/php}
IF通过
{php} endif; {/php}
模板编译后的结果就是:
<?php if (is_array($info)): ?>
IF通过
<?php endif; ?>
实际上SW-X模板引擎中的foreach
和if
语句都是通过{php}
标签解析原生php缩写语法实现的。
包含文件
在当前模版文件中包含其他的模版文件使用include标签,标签用法(只能用双引号):
{include file="视图文件地址" }
例如:
<--表示:引入/app/view/public/menu.html文件-->
{include file="public/menu" /}
验证码
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])
的方式实现循环上传。
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, '描述');
}
}
输出返回值
WebSocket控制器中输出返回值跟HTTP控制器一样,都是调用fetch()
方法,只不过传入的参数格式不一样。
return $this->fetch(状态值, 描述, 返回值[默认是空数组])
最终的返回格式如下:
{
"action":"状态值",
"msg":"描述",
"data":"返回值",
}
注解实现方式
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="注入的成员属性名称")
SW-X中支持的所有注解元如下,也可以参考这个作为注解的使用规范:
/**
* @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")
* @RequestMapping(route="/index", method="GET|POST", title="路由描述")
* @Controller(prefix="user")
* @onRoute
*/
每个注解元的解释如下:
Ioc
:属性注入AopBefore
:AOP前置操作AopAfter
:AOP后置操作AopAround
:AOP环绕操作AopThrows
:AOP异常转发RequestMapping
:方法对应的路由绑定Controller
:控制器的前置路由绑定onRoute
:申明某个方法不能被路由访问
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);
}
public static $Db2;
/**
* 由于我们是静态的,所以如果我需要使用Ioc注入,则需要提前创建好对应的成员属性
* @Ioc(class="\x\Db", name="Db2")
*/
public static function test() {
var_dump(self::$Db2);
}
}
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($request, $response) {
return true;
}
// 后置
public function after($request, $response) {
return true;
}
// 环绕
public function around($request, $response) {
return true;
}
// 异常通知
public function throws($request, $response, $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,你并不能访问我');
}
}
支持范围
SW-X的路由只对HTTP
、WebSocket
服务有效,并且如果是WebSocket
服务,则必须启用框架处理模式。
路由模式
SW-X的路由模式是共用的,也就是说,不管是HTTP
还是WebSocket
服务,都是可以公用框架的路由配置,具体看:HTTP服务路由篇
请求类型过滤
在路由注解元中我们通过method
参数可以前置限制路由的请求类型,若请求不符合,则会被框架调用404重定向逻辑,具体配置404参考:自定义404篇
获取全站路由表
可能会有开发者有疑问,如果注解中使用了很多路由规则,那我要怎么知道都有哪些路由呢?为了应对这个问题,SW-X提供了获取应用全路由表的方法。
具体使用方法如下:
\x\route\Table::route(); // 获取HTTP服务的全应用路由
\x\route\WebSocket::route(); // 获WebSocket服务的全应用路由
最终返回的数组结构是个多维数组,下面是可能存在的节点:
[
'路由地址' => [
'n' => 命名空间地址,
'name' => '方法名称',
'method' => '请求类型',
'title' => '路由描述',
'father' => 父class的注解
'own' => function本身的注解
]
]
数据库连接池说明
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()
方法,将连接归还到连接中。
这是一个必须遵守的规范。
name
name(表名)
:选择操作表。
SQL语句构造器,表前缀使用/config/mysql.php
配置里的,prefix
字段。
所以我们使用name()
链的时候不需要带表前缀。其使用demo如下:
$Db = new \x\Db();
$Db->name('user');
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语句中是可以多次使用的,其执行顺序是先进先执行,相同的语句并不会覆盖,所以使用的时候需要自己注意下。
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: 等同于 JOIN(默认的JOIN类型)
,如果表中有至少一个匹配,则返回行
LEFT 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);
更多的链式组合可以自己尝试下。
delete
delete()
:是链式操作的终结方法之一,用于构造删除语句,该链只支持主要的where查询表达式,不支持:order
、page
、limit
类型的表达式。
该方法调用后会返回最终构造成的SQL语句。
示例:
$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语句。
示例:
$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);
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;
执行原生SQL
SW-X中支持直接调用query()
方法执行原生的SQL语句,但其中的表名称并不能使用到配置文件中的表前缀配置项。
$Db = new \x\Db();
$Db->name('user')->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
的业务,否则会查询事务隔离级别的报错。
配置说明
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();
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 日志类文件存放根地址,末尾带/符号