liuxingwei / simple-api-framework
A simple API framework.
Installs: 12
Dependents: 0
Suggesters: 0
Security: 0
Stars: 2
Watchers: 1
Forks: 0
Open Issues: 0
pkg:composer/liuxingwei/simple-api-framework
Requires
- php: >=5.6.0
- ext-json: *
- ext-mbstring: *
- ext-pdo: *
- doctrine/annotations: ^1.8
- liuxingwei/lxw-php-di: ^6.0
- dev-master
- 0.0.53
- 0.0.52
- 0.0.51
- 0.0.50
- 0.0.49
- 0.0.48
- 0.0.47
- 0.0.46
- 0.0.45
- 0.0.44
- 0.0.43
- 0.0.42
- 0.0.41
- 0.0.40
- 0.0.39
- 0.0.38
- 0.0.37
- 0.0.36
- 0.0.35
- 0.0.34
- 0.0.33
- 0.0.32
- 0.0.31
- 0.0.30
- 0.0.29
- 0.0.28
- 0.0.27
- 0.0.26
- 0.0.25
- 0.0.24
- 0.0.23
- 0.0.22
- 0.0.21
- 0.0.20
- 0.0.19
- 0.0.18
- 0.0.17
- 0.0.16
- 0.0.15
- 0.0.14
- 0.0.13
- 0.0.12
- 0.0.11
- 0.0.10
- 0.0.9
- 0.0.8
- 0.0.7
- 0.0.6
- 0.0.5
- 0.0.4
- 0.0.2
- 0.0.1
- dev-add-license-1
This package is auto-updated.
Last update: 2025-10-24 05:48:26 UTC
README
零、概述
SAF(Simple Api Framework)是一个极简单的PHP API开发框架,适用于前后端分离架构的web项目,作为后端PHP API服务的框架。
说其极简,一方面是因为它只有很少的核心类和几个支持文件,另一方面是因为它只支持有限的场景,当然,也是说它非常易于使用。
SAF没有Beautiful URL路由,GET请求的参数是通过形如name=zhangsan&sex=male的QueryString参数传递的。
SAF遵循了惯例优于配置的理念,API必须放在指定的目录(项目目录的Application\Api)下,且GET请求对应的API类要放在Get子目录,而POST请求对应的API类要放在Post子目录,其他类型的HTTP请求对应的API,也放在与其HTTP METHOD相对应的子目录。
数据库方面,SAF有一个简单的DB类,它是以PDO为底层的,理论上它可以支持多种数据库服务,但是目前只在MySQL上做过测试。因此最适合的数据库搭配就是MySQL5.7+。
对于PHP,由于7.2.*和其前的版本,在trait特性支持上有缺陷(引用继承了同一trait的多个trait时,会导致重复定义方法的致命错误),因此建议PHP 7.3+。
一、环境要求
支持PHP 5.6,建议PHP 7.3+,MySQL 5.7+。
二、下载
1. composer
composer create-project liuxingwei/simple-api-framework
2. github
git clone https://github.com/Liuxingwei/php-simple-api-framework.git
三、环境搭建
有如下几种方式,任选其一(前两种方式仅适用于开发、测试环境):
1、PHP Built-in Server
使用PHP内建服务时,无需nginx、apache,只需PHP。
在命令行下,切换至public文件夹(safpath指SAF的路径),执行php -S localhost:xxxx index.php即可,其中xxxx为端口号。
示例:
>cd safpath/public >php -S localhost:xxxx index.php
浏览器打开localhost:xxxx,看到如下内容,服务启动成功:
Please access detail API.
2、借助 VSCode 中 PHP Server 插件
安装PHP Server插件,打开File > Preferences > Settings,找到Extensions > PHP Server Configuration,将Relative Path改为./public。
如果PHP可执行文件没加到系统路径中,可以将其填写在PHP Server插件的PHP Path配置项中。
打开public/index.php文件,在文件窗口右键,选PHP Server: Server Project。
浏览器打开localhost:xxxx,看到如下内容,服务启动成功:
Please access detail API.
PHP Server: Stop可以停止服务。
PHP Server: Reload Server可以重启服务。
3、使用 WAMP
启动WAMP,点进托盘区WAMP图标,选择Apache > httpd-vhosts.conf文件。
在打开的文件中,复制VirtualHost段,修改端口,并将路径修改为项目目录的public文件夹。
示例(safpath即指SAF的路径):
要复制的段:
<VirtualHost *:80> ServerName localhost ServerAlias localhost DocumentRoot "${INSTALL_DIR}/www" <Directory "${INSTALL_DIR}/www/"> Options +Indexes +Includes +FollowSymLinks +MultiViews AllowOverride All Require local </Directory> </VirtualHost>
复制后修改IP和DocumentRoot、Directory路径:
<VirtualHost *:xxxx> ServerName localhost ServerAlias localhost DocumentRoot "safpath/public" <Directory "safpath/public"> Options +Indexes +Includes +FollowSymLinks +MultiViews AllowOverride All Require local </Directory> </VirtualHost>
修改后的完整文件:
<VirtualHost *:80> ServerName localhost ServerAlias localhost DocumentRoot "${INSTALL_DIR}/www" <Directory "${INSTALL_DIR}/www/"> Options +Indexes +Includes +FollowSymLinks +MultiViews AllowOverride All Require local </Directory> </VirtualHost> <VirtualHost *:xxxx> ServerName localhost ServerAlias localhost DocumentRoot "safpath/public" <Directory "safpath/public"> Options +Indexes +Includes +FollowSymLinks +MultiViews AllowOverride All Require local </Directory> </VirtualHost>
重启Apache。
浏览器打开localhost:xxxx,看到如下内容,服务启动成功:
Please access detail API.
4. nginx
添加一个server配置:
server {
        listen xxxx default_server;
        listen [::]:xxxx default_server;
        server_name _;
        root safpath/public;
        location / {
                try_files $uri $uri/ =404;
                if (!-e $request_filename) {
                    rewrite  ^(.*)$  /index.php$1  last;
                }
        }
        location ~ \.php(.*)$ {
                include snippets/fastcgi-php.conf;
                fastcgi_split_path_info ^(.+\.php)(/.*)$;
                # With php-fpm (or other unix sockets):
                fastcgi_pass unix:/var/run/php/php7.3-fpm.sock;
                # With php-cgi (or other tcp sockets):
                # fastcgi_pass 127.0.0.1:9000;
        }
        location ~ /\.ht {
                deny all;
        }
}
重启nginx。
四、目录结构
初始的项目目录结构如下:
+ application
  + Api
    + Delete
      + Example
        - Index.php
    + Get
      + Example
        - Index.php
    + Patch
      + Example
        - Index.php
    + Post
      + Example
        - Index.php
    + Put
      + Example
        - Index.php
  + Model
    - SafExample.php
  + Validations
    - MyValidation.php
+ conf
  + err_define
    - cn.php.sample
    - default.php
  - config.php.sample
  - di_config.php.sample
  - env.php.sample
+ doc
  - DB-Class-Usage.md
+ lib
  + Core
    - App.php
    - BaseApiInterface.php
    - BaseModel.php
    - bootstrap.php
    - DB.php
    - ErrorCode.php
    - ErrorCodeTrait.php
    - Request.php
    - Response.php
    - SafException.php
  + Validations
    - AbstractValidation.php
    - Length.php
    - Limit.php
    - NotEmpty.php
    - Required.php
    - Rule.php
+ public
  - .htaccess
  - index.html
  - index.php
+ vendor
  + bin
  + composer
  + doctrine
  + jeremeamia
  + liuxingwei
  + nikic
  + php-di
  + psr
  + symfony
  - autoload.php
- .gitignore
- composer.json
- composer.lock
- LICENSE
- README.md
五、创建API
在application/Api/Get或application/Api/Post中根据业务需要创建一个子文件夹(也可以是多级文件夹),在其中创建一个API类。
该类实现Lib\Core\Interfaces\BaseApi接口,并实现run()方法,该方法签名为:run(array $param):mixed。
例如,在Get文件夹创建Example文件夹,并在其中创建Index.php,文件内容如下:
<?php namespace Application\Api\Get\Example; use Lib\Core\Interfaces\BaseApi; class Index implements BaseApi { public function run(array $params) { $result = [ 'code' => 200, 'message' => 'OK', 'description' => "I'm a GET request.", ]; return $resut; } }
run()方法的参数即为HTTP请求的参数集合。
此时,向服务器的/example/index发出GET请求,即可收到值为
{ "code": 200, "message": "OK", "description": "I'm a GET request." }
的json返回。
此时,向/example/index发出POST请求,收到的则是
{ "code": 404, "message": "API /example/index 不存在" }
要创建一个接受/example/index的POST请求的API,需要在application/Api/Post中创建Example文件夹,并在Example中创建Index.php文件:
<?php namespace Application\Api\Post\Example; use Lib\Core\Interfaces\BaseApi; class Index implements BaseApi { public function run(array $params) { $result = [ 'code' => 200, 'message' => 'OK', 'description' => "I'm a POST request.", ]; return $result; } }
命名空间与uri的关系
API类遵循psr4标准,其命名空间Application\Api映射于项目根目录中的application/Api目录。
API类名与API的uri之间的关系是,将类的命名空间中的Application\Api\xxx部分去除,并将\转换为/,就是该API的uri,而其中的xxx即对应了HTTP METHOD。
例如类Application\Api\Get\Example\Index对应的API的uri即为/example/index,相应的HTTP METHOD为GET。
而类Application\Api\Post\Example\Index对应的API的uri也为/example/index,但相应的HTTP METHOD为POST。
PUT、PATCH、DELETE等HTTP METHOD类推。
由于HTTP定义的url对于大小写不敏感,在转换为类名时,会自动将每部分的首字母转换为大写,并将短横线及其后的一个字母转换为大写字母,以对应命名空间中的大写字母。
GET /example/index          =>    Application\Api\Get\Example\Index
POST /user-info/create-user    =>    Application\Api\Post\UserInfo\CreateUser
PUT /user-info/modify-user   =>    Application\Api\Put\UserInfo\ModifyUser
run()方法的参数
run()方法的params参数接收了与HTTP METHOD相对应的request数据。
对于POST、PUT、PATCH,如果提交的HEADER中Content-Type为application/json的情况,接收了json解码后的Request Payload数据。
对于PUT、PATCH的application/x-www-form-urlencoded,接收了(解析后的)Form Data数据,类似于$_POST的值。
对于POST的form-data、multipart/form-data、application/x-www-form-urlencoded,则接收了(解析后的)Form Data数据。相当于$_POST的值。
对于GET和DELETE,则接收了解析后的Query String键值对。相当于$_GET的值。
其它情况,则直接在params的BODY元素中存储了提交的Request Payload的原始值。
ErrorCode类
可以将返回的基本结构放在配置文件中,通过ErrorCode类读取。
框架提供了一个ErrorCodeTrait,其中定义了实例变量errCode,实例化了ErrorCode类,可以直接在需要使用ErrorCode的类中使用(注意规避一下与errCode变量名的命名冲突):
use Lib\Core\ErrorCodeTrait; class xxx { use ErrorCodeTrait; public function xx(...) { ... $res = $this->errCode->OK; return $res; } }
ErrorCode使用的配置文件有default.php和xx.php两个,后一个文件的xx指的是配置语言,默认为中文,即cn。配置文件默认放在项目根目录下的conf/err_define文件夹。
文件位置和语言均中在conf/config.php中配置:
return [ ... 'err_define_dir' => __DIR__ . '/mydefine', 'language' => 'en', ... ]
配置方式参见conf/err_define/cn.php。
default.php中放置的是系统预定义的配置,不建议直接修改,可以在语言文件中定义同名元素覆盖默认配置。
配置中的元素的key,可以当作errCode的属性直接使用:
$this->errCode->OK; $this->errCode->PARAM_MUST_NOT_EMPTY;
可以这样改写Example\Index:
<?php namespace Application\Api\Get\Example; use Lib\Core\Interfaces\BaseApi; use Lib\Core\ErrorCodeTrait; class Index implements BaseApi { use ErrorCodeTrait; public function run(array $params) { $res = $this->errCode->OK; $res['descriptio' => "I'm GET request']; return $res; } }
消息定义可以使用占位符,占位符被包含在{{:和}}之间。可以使用数组指定要替换的与数组键匹配的值。
例如,定义如下消息:
PARAM_NOT_EXISTS => ['code' => 403, 'message' => '参数 {{:param}} 的长度必须在 {{:min}} 到 {{:max}} 之间'];
然后在Api的run()方法中这样使用:
$err = self::errCode->mapError(self::errCode->PARAM_NOT_EXISTS, ['param' => 'username', 'min' => 3, 'max' => 16]); // 最终的输出结果为: // { // "code": 403, // "message": "参数 username 的长度必须在 3 到 16 之间" // }
结束执行
框架最后调用API代码就是run()方法,因此该方法执行的最后一行代码就标志了API的执行结束。
下面的代码示例了在不同条件下的不同输出并结束API的执行:
public function run(array $params) { if ($signinSuccess) { return [ 'code' => 200, 'message' => '登录成功', ]; } return [ 'code' => 200, 'message' => '登录失败', ]; }
默认的输出中,HTTP CODE均为200。
如果认为登录失败是一种错误,可以定义errorCode的属性:
public $errorCode; public function run(array $params) { if ($signinSuccess) { return [ 'code' => 200, 'message' => '登录成功', ]; } $this->errorCode = '403'; return [ 'code' => 403, 'message' => '登录失败', ]; }
如果定义的code和HTTP错误码相同,还可以使用SafException类的throw静态方法抛异常:
public function run(array $params) { if (!$signinSuccess) { SafException::throw([ 'code' => 403, 'message' => '登录失败', ]) } return [ 'code' => 200, 'message' => '登录成功', ]; }
这两段代码的输出是一样的。
输出类型
默认的输出类型为application/json。无需主动对结果进行json_encode,只需run()方法返回要输出的数组即可。
public function run($param) { ...... return ['code' => '200', 'message' => 'ok']; }
也可以定义其它类型的输出,通过API类的$responseType属性指定。
...... class xxx extends BaseApiInterface { public $responseType = 'html'; return "<div>这是一个html片段</div>"; }
可以使用的类型有:
html、json、xml、text、javascript、steam。
其中,json和xml类型,run()方法返回数组;html和text返回文本;javascript返回js文本。
stream用于输出文件,除了run()方法要返回待输出文件的内容外,还需要通过headers属性指定response头信息。
namespace Application\Api\Get\Test; use Lib\Core\BaseApiInterface; class Stream implements BaseApiInterface { public $responseType = 'stream'; public $headers = [ 'Content-Type: application/vnd.ms-excel', 'Content-Disposition: attachment;filename=test.csv', 'Cache-Control: max-age=0', ]; public function run(array $request) { $csv = "name,age,sex,job\nzhangsan,30,男,程序猿"; $this->headers[] = 'Content-Lenght: ' . mb_strlen($csv); return $csv; } }
配置文件
通用的配置文件放置在conf目录中,文件名为config.php,框架提供了一个示例文件config.php.sample。
该文件内容示例如下:
<?php return [ 'runtime' => 'development', // 运行环境,development 为开发环境,test 为测试环境,product 为生产环境 'api_path' => '/Api', 'db' => [ // 数据库配置 'dbms' => 'mysql', 'host' => '127.0.0.1', 'port' => '3306', 'user' => 'root', 'password' => '123456', 'dbname' => 'sampledb', ], 'second_db' => [ 'dbms' => 'mysql', 'host' => '127.0.0.1', 'port' => '3306', 'user' => 'root', 'password' => '123456', 'dbname' => 'jol', ], 'di_config' => [ // PHP-DI 定义配置,可以是定义文件名,也可以是定义文件名数组 __DIR__ . '/di_config.php', ], 'debug' => true, // 是否开启 debug,开启 debug 后,可以在提交中带有 debug 参数,返回的数据中将有 debug 项 ];
其中的配置数组将被读入超全局变量$_ENV的config元素中。
上面示例中的变量可以这样读取:
$_ENV['config']['runtime']; // 'development' $_ENV['config']['db']['host']; // 'localhost'
为方便使用,该配置也被定义在CONFIG常量中,相同的配置还可以这样读取:
CONFIG['runtime']; CONFIG['db']['host'];
通常情况下,我们的生产环境和测试、开发环境在配置方面总会有些差别,因此SAF提供了可以覆写conf/config.php文件中的默认配置的方法:
即在conf/env.php文件中放置需要覆写的配置项,比如生产环境的数据库主机地址为10.0.0.1,密码为@77pai*654,则可以在生产服务器的conf/env.php文件作如下配置:
return [ 'db' => [ 'host' => '10.0.0.1', 'password' => '@77pai*654' ], 'second_db' => [ 'host' => '10.0.0.1', 'password' => '@77pai*654' ] ];
而与conf/config.php相同的配置,则无需在conf/env.php中重复配置。
不要将conf/config.php文件提交到版本库,可以将其放入版本库的忽略文件列表中,而额外提供一个conf/env.php.sample,作为配置的参考。
DB和Model
框架实现了一个基于PDO的DB类,具体使用请参考doc目录的DB-Class-Usage.md文件。
框架提供了一个BaseModel基类,可以以此为基础自定义Model类,继承BaseModel类。建议将Model类定义在Application\Model命名空间中。
// application/Model/User.php namespace Application\Model; use Lib\Core\BaseModel; class User extends BaseModel { public function checkUser($userName, $password) { return $this->where('user_name = :user_name AND password = :password', [':user_name' => $userName, ':password' => $password])->selectOne(); } }
上例中,是假定表名与类名相同,都是user(在MySQL中不区分大小写)。
如果实际的表名与类名不同,则需要使用$table属性自定义表名:
// application/Model/UserInfo.php namespace Application\Model; use Lib\Core\BaseModel; class UserInfo extends BaseModel { protected $table = 'user_info'; public function checkUser($userName, $password) { return $this->where('user_name = :user_name AND password = :password', [':user_name' => $userName, ':password' => $password])->selectOne(); } }
框架会将类名定义中的大写字母转换「下划线加小写」字母的形式。
注意不要在MySQL中用驼峰法命名表名,而要用下划线命名法。
上例也可以省略表名的显式定义:
// application/Model/UserInfo.php namespace Application\Model; use Lib\Core\BaseModel; class UserInfo extends BaseModel { public function checkUser($userName, $password) { return $this->where('user_name = :user_name AND password = :password', [':user_name' => $userName, ':password' => $password])->selectOne(); } }
如果使用依赖注入方式注入Model类,建议以多实例模式注入,以避免单例引起的问题。
可以在定义Model时即将其指定为多实例模式:
// application/Model/UserInfo.php namespace Application\Model; use Lib\Core\BaseModel; /** * @Scope('prototype') */ class UserInfo extends BaseModel { ..... }
依赖注入支持
框架提供了对依赖注入的支持。使用了改造过的LIUXINGWEI/LXW-PHP-DI(fork 自PHP-DI/PHP-DI)。主要是增加了Scope注解和scope()方法,以支持非单例注入模式。
Scope注解用于自动装配类的定义,其参数可以是singleton或prototype,不写Scope注解,或者不写Scope的参数,均默认为singleton,即单例模式,prototype则为非单例模式。参见Application/Model/SafExample类的定义。
scope()方法用于依赖注入配置,在调用factory()方法之后,链式调用scope()方法,其参数为singleton或prototype,分别对应单例和非单例模式。参见conf/config.php.sample文件中的定义。
两种非单例注入模式的注入示例见Application/Api/Get/Example/Index。
默认的配置文件是conf/di_config.php,不过可以通过系统配置文件conf/config.php中的di_config项来修改。
该配置项可以是一个PHP-DI配置的路径,也可以是一个包含多个PHP-DI配置文件路径的数组。
框架在conf目录放置了一个依赖注入配置的示例文件conf/di_config.php.sample。
有关PHP-DI的详细使用请查阅PHP-DI 文档。
示例API中Get\Example\Index中有依赖注入示例。
虚拟子目录支持
如果需要将API部署在虚拟子目录中,需要将请求转发至public\index.php,由其负责路由。
同时,需要修改conf\config.php中的api_path设置,将虚拟目录放在该参数中,例如为所有API提供/Api路径前缀:
'api_path' => '/Api'
AJAX 跨域问题
如果将SAF用于AJAX调用的API服务,可能需要跨域。
跨域配置项如下:
'cross_domain' => [ 'enable' => true, 'domain' => 'http://192.168.1.25:8080', 'methods' => 'POST, GET, PUT, DELETE, PATCH', 'headers' => 'sign, key', ];
enable配置项决定了是否启用跨域。系统默认是不启用。
domain的系统默认值为*。
methods的系统默认值为'POST, GET, PUT, DELETE, PATCH'。
headers的系统默认值为'x-requested-with, content-type, debug'。
由于系统需要使用header的默认值支持,因此此项配置不会覆盖系统默认值,而是会与系统默认值合并。其余三项,则会由用户配置覆盖系统默认值。
参数校验
框架提供了几个基本的校验类,用于对请求参数进行校验。
这些校验方法的使用依赖了annotation(注解)技术。仅需在run()方法上添加注解,即可实现对参数的校验。
每条注解需要声明要校验的参数名,有的还需要带有额外的参数。
已经实现的校验注解及其示例如下(所有注解的字符串类型必须使用双引号作为定界符,使用单引号会引发错误):
1. Required
Required注解用于参数必须的情况。它只要求参数存在,对于参数值则没有要求。
/** * ...... * @Required("user_name") * @Required("password") */ public function run($params) { ... }
如上代码要求$params参数数组中必须包含user_name和password两个元素。
2. NotEmpty
NotEmpty注解用于参数不得为空的情况。
/** * ...... * @NotEmpty("description") */ public function run($params) { ... }
如上代码校验参数$params中的user_name元素不得为空。
需要注意的是NotEmpty注解不对参数是否存在进行校验,它仅在要校验的参数存在的情况下才有效。
如果要求参数必须存在且不得为空,需要联合Required注解共同完成校验:
/** * ...... * @Required("user_name") * @NotEmpty("user_name") */ public function run($params) { ... }
NotEmpty支持对要校验的参数去首尾空格:
/** * ..... * @NotEmpty("user_name", trim=true) */ public function run($params) ......
不过,trim仅存在于校验过程中,对实际参数没有影响,因此在run()方法内部,仍需自己处理参数的首尾空格问题。
3. 长度校验
对长度的校验有两个注解,Length适用于字符串,Limit适用于数值。
Length注解有max和min两个可选参数,分别限制最大(含)和最小长度(含),为闭区间。
/** * ...... * @Length("password", max=16, min=9) */ public function run($params) ......
Limit注解也是max和min两个可选参数,闭区间。不过它支持浮点数。
/** * ...... * @Limit("price", min=12.5, max=13.3) */ public function run($params) ......
4. 正则校验类
对于电话号码、IP地址、邮编、Email等进行合法性校验时,会使用到正则表达式。
Rule注解就用于这种场合。
/** * ...... * @Rule("ip", rule=".*?@.*?\..*?", error={"message":"参数 \"IP\" 不是合法的 IP 地址"}) */ public function run($params) ......
与前面的校验类不同,正则校验类允许自定义校验失败时的消息,见上例中的error参数。
自定义校验类
可以自定义校验类,具体写法可以参照Lib\Validatetions中的预置校验类。
简单的说,校验类是依赖doctrine/annotations实现的。
首先,自定义校验类要继承Lib\Validations\AbstractValidation类,并在类前面添加@Annotation和@Target({”METHOD"})注解:
use Lib\Validations\AbstractValidation`; /** * @Annotation * @Target({"METHOD"}) */ class MyValidation extends AbstractValidation { ...... }
校验类须实现check()方法,其参数即为框架转换后的请求参数(也即run()方法接收到的参数,见前言run()方法的参数。
在校验通过时,check()方法返回true,失败时返回false。
并且在失败时,要设置err变量,其类型为数组,包括code和message两个元素,对应失败的编码和原因。
...... class MyValidation extends AbstractValidation { ...... public function check(array $params) { if (...) { // 校验失败的处理 $this->err = [ 'code' => 10086, 'message' => '客服小姐姐脾气太大' ]; return false; } return true; } }
校验类的蕨类定义了一个变量value,它对应于校验注解的同名参数,如果该参数位于注解的第一位,可以不标名:
...... class MyValidation extends AbstractValidation { public function check(array $params) { $this->value; ...... } }
...... class MyApi implements BaseApiInterface { /** * @MyValidation(value="username") */ public function run($params) { } }
class MyApi1 implements BaseApiInterface { /** * @MyValidation("username") */ public function run($params) { ...... } }
上面例子中的两种注解,其效果是一样的,在MyValidation类中的value获取的值均为username。
校验类的其它公有变量,对应于注解中的同名参数。
书写注解时,要注意,如果参数类型为string,则对应的参数值要使用双引号,而不能用单引号。如果参数类型是array,要放在一对花括号中,其格式与标准json基本一致。
以下是一个比较完整的示例:
namespace Application\Validations; use Lib\Validations\AbstractValidation; /** * 利用正则校验参数是否符合规则 * @Annotation * @Target({"METHOD"}) */ class MyValidation extends AbstractValidation { /** * 出错时的自定义消息 * @var array */ public $error = null; /** * 校验用的正则表达式 * @var string */ public $rule; public function check(array $params) { if (key_exists($this->value, $params)) { // 判断要校验的参数在给出的参数中是否存在,存在才需要校验 if (preg_match('/' . $this->rule . '/', $params[$this->value])) { // 用给定的正则进行匹配,成功返回 true return true; } else { // 失败对 $this->err 进行设置,并返回 false $code = (key_exists('code', $this->error)) ? $this->error['code'] : 10086; $message = (key_exists('message', $this->error)) ? $this->error['message'] : '参数格式不符合要求'; $this->err = [ 'code' => $code, 'message' => $message ]; return false; } } else { // 要校验的参数不存在,无需校验直接返回 true return true; } } }
use Application\Api\Get\Test; use Lib\Core\BaseApiInterface; use Lib\Core\ErrorCodeTrait; class Index implements BaseApiInterface { use ErrorCodeTrait; /** * ...... * @MyValidation("ip", rule="^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$", error={"code":"10090","message":"不是合法的IP地址"}) */ public function run($params) { ...... } }
不建议将自定义校验类放在Lib\Validations命名空间,在升级框架时可能会受影响。
可以将自定义校验类放在框架可识别的任意命名空间中,并在配置文件中使用validation_namespaces对其进行标识。上面的示例就是将校验类放在Application\Validations命名空间中,其在配置文件中的定义如下:
return [ ... 'validation_namespaces' => [ 'Application\Validations', ], ... ];