SW-X

SW-X 是一款基于Swoole 实现的常驻内存便捷开发式PHP框架,专为高性能、便捷开发而生,摆脱传统的FPM运行模式。 在开发上,我们为您准备了以下常用组件:

  • http 服务服务器
  • websocket 服务服务器
  • server 服务服务器
  • 数据库ORM
  • 图片验证码
  • 模板渲染引擎
  • 协程redis连接池
  • 协程mysql 连接池
  • 注解路由
  • 注解Ioc
  • 注解Aop

以上组件为常用组件,更多组件请看组件库文档

维护团队

  • 作者
    • 小黄牛 1731223728@qq.com
  • 团队成员
    • 暂无,期待您的加入

其他

    QQ交流群

    • SW-X官方一群 1012826310
  • 商业支持:

    • QQ 1731223728
  • 作者微信

    • junphper

注意事项

  • 不运行在代码中执行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的所有消息事件,并在onRequestonMessage事件中进行了二次开发,进而实现路由解析功能。
如果开发者需要二次使用消息事件,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组件中方便客户端与服务端交互的对象,它使用了对象池对象复用模式,以及注入requestresponse对象进行数据交互

加载流程

  • 用户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配置文件下的404error_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模板引擎中的foreachif语句都是通过{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.phpfile节点下。

<?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_filessl_key_file证书路径即可。

自定义请求处理

很多时候官方预设的JSON通信方式并不一定适合所有开发者,所以SW-X支持自定义自己的通信方式,只需要修改/config/websocket.php配置中的is_onMessagefalse即可。
这样就表示框架不再监听onMessage事件,改由自己监听/app/event/下的onMessage事件,进而由自己实现数据分包。

官方请求处理

/config/websocket.php配置中的is_onMessagetrue时【默认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的路由只对HTTPWebSocket服务有效,并且如果是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种查询表达式,主要用于查询一条记录。主要不支持limitpage链。
该方法调用后会返回最终构造成的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查询表达式,不支持:orderpagelimit类型的表达式。
该方法调用后会返回最终构造成的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查询表达式,不支持:orderpagelimit类型的表达式。
该方法调用后会返回最终构造成的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查询表达式,不支持:orderpagelimit类型的表达式。
该方法调用后会返回最终构造成的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查询表达式,不支持:orderpagelimit类型的表达式。
该方法调用后会返回最终构造成的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 日志类文件存放根地址,末尾带/符号