本人之前用Yii2框架做了一些http接口开发,一般使用模型(yii\base\Model)对数据进行验证。当接口较少的时候还是很方便的,但是如果接口比较多,一个模型中的规则集可能会变得庞大,维护起来就有点麻烦。
后来想到Yii2支持基于类的action,同时还有动态模型可以使用(yii\base\DynamicModel),就想组装一下,在action中实现对数据的基本验证,即一个接口对应一个action类文件,数据验证规则写在action类中,不同接口的验证规则不会放在一起,减少维护的麻烦。
下面就拿个演示项目来讲一下具体是如何实现:
创建项目
常规做法了,进入nginx的web根目录
composer create-project --prefer-dist -vv yiisoft/yii2-app-basic yii2-api-demo
目录结构
为实现动态模型进行接口数据验证,新建了若干目录及类文件,详见下图:
代码-接口基类
namespace app\common\base;
use app\common\validators\ValidateModel;
use yii\base\Action;
use yii\web\Response;
class BaseAction extends Action
{
/**
* @var array
*/
public $params = [];
/**
* 验证属性
* @return array
*/
protected function attributes() {
return [];
}
/**
* 验证规则
* @return array
*/
protected function rules() {
return [];
}
/**
* 初始化
*/
function init() {
// 初始化输入参数
$request = \Yii::$app->request;
$this->params = array_merge($request->get(), $request->bodyParams);
}
/**
* 创建验证模型
* @param array $data
* @param array $append
* @return ValidateModel
* @throws \yii\base\InvalidConfigException
*/
protected function createValideModel(array $data = [], array $append = []) {
if (empty($data)) {
$input = file_get_contents('php://input');
$data = array_merge(\Yii::$app->request->get(), $input ? json_decode($input, true): \Yii::$app->request->bodyParams, $append);
}
return ValidateModel::create($this->attributes(), $this->rules(), $data);
}
/**
* 执行验证
* @param array $data
* @param array $append
* @return ValidateModel
* @throws \Exception
*/
protected function validate(array $data = [], array $append = []) {
return $this->createValideModel($data, $append)->execute();
}
/**
* 成功响应
* @param array $data
* @param string $message
* @return array
*/
protected function success($data = [], $message = '') {
\Yii::$app->response->format = Response::FORMAT_JSON;
return ['code' => 0, 'message' => $message, 'data' => $data];
}
/**
* 失败响应
* @param array $data
* @param string $message
* @return array
*/
protected function error($message, $data = []) {
\Yii::$app->response->format = Response::FORMAT_JSON;
return ['code' => 1, 'message' => $message, 'data' => $data];
}
}
namespace app\common\validators;
use yii\base\DynamicModel;
use yii\base\InvalidConfigException;
use yii\validators\Validator;
class ValidateModel extends DynamicModel
{
/**
* 创建
* @param array $attributes
* @param array $rules
* @param array $data
* @return static
* @throws InvalidConfigException
*/
public static function create(array $attributes, array $rules = [], array $data = []) {
$attributes = self::fillAttributes($attributes, $data);
$model = new self($attributes);
return $model->addRules($rules);
}
/**
* 填充属性集
* @param array $attributes
* @param array $data
* @return array
*/
protected static function fillAttributes(array $attributes, array $data = []) {
$attrs = [];
$data || $data = array_merge(\Yii::$app->request->get(), \Yii::$app->request->bodyParams);
foreach ($attributes as $key) {
$attrs[$key] = $data[$key] ?? null;
}
return $attrs;
}
/**
* @param array $rules
* @return static
* @throws InvalidConfigException
*/
public function addRules(array $rules) {
$validators = $this->getValidators();
foreach ($rules as $rule) {
if ($rule instanceof Validator) {
$validators->append($rule);
} elseif (is_array($rule) && isset($rule[0], $rule[1])) { // attributes, validator type
$validator = Validator::createValidator($rule[1], $this, (array)$rule[0], array_slice($rule, 2));
$validators->append($validator);
} else {
throw new InvalidConfigException('Invalid validation rule: a rule must specify both attribute names and validator type.');
}
}
return $this;
}
/**
* 执行验证
* @return static
* @throws \Exception
*/
public function execute() {
$this->validate();
if ($this->hasErrors()) {
throw new \Exception($this->getFirstErrorsText());
}
return $this;
}
/**
* 获取第一个错误文本信息
* @param bool $all
* @return string
*/
public function getFirstErrorsText(bool $all = false)
{
$firstErrors = parent::getFirstErrors();
if ($firstErrors) {
return $all ? json_encode($firstErrors) : current($firstErrors);
} else {
return '';
}
}
/**
* 获取属性集
* @param bool $canNull 是否返回值为null的属性
* @return array
*/
public function attrs(bool $canNull = false) {
$attrs = [];
foreach ($this->getAttributes() as $name => $val) {
if ($canNull || $val !== null) {
$attrs[$name] = $val;
}
}
return $attrs;
}
}
namespace app\controllers\actions\demo;
use app\common\base\BaseAction;
class IndexAction extends BaseAction
{
/**
* 入口
*/
public function run()
{
try {
// 模拟数据
$data = [
['id' => 1, 'name' => '赵'],
['id' => 2, 'name' => '钱'],
['id' => 3, 'name' => '孙'],
['id' => 4, 'name' => '李'],
['id' => 5, 'name' => '周'],
['id' => 6, 'name' => '吴'],
['id' => 7, 'name' => '陈'],
['id' => 8, 'name' => '王'],
['id' => 9, 'name' => '冯'],
['id' => 10, 'name' => '陈'],
['id' => 11, 'name' => '诸'],
['id' => 12, 'name' => '卫'],
['id' => 13, 'name' => '蒋'],
['id' => 14, 'name' => '沈'],
['id' => 15, 'name' => '王'],
['id' => 16, 'name' => '杨'],
];
// 数据验证
$attrs = $this->validate()->attrs();
// 根据name查找
$list = $attrs['name'] ? array_filter($data, function ($v) use($attrs) {
return $v['name'] == $attrs['name'];
}) : $data;
// 模拟分页
$list && $list = array_slice($list, ($attrs['page'] - 1) * $attrs['rows'], $attrs['rows']);
return $this->success(['total' => count($list), 'list' => $list]);
} catch (\Exception $e) {
return $this->error($e->getMessage());
}
}
/**
* 可接受的参数字段,rules方法中用到的字段必须先在此定义
* @return array
*/
public function attributes()
{
return ['name', 'page', 'rows'];
}
/**
* 验证规则,与系统模型中的规则一样的
* @return array
*/
public function rules() {
return [
['name', 'string', 'message' => '`名称`不合法'],
['name', 'filter', 'filter' => function($v) { return trim($v); }],
['page', 'default', 'value' => 1],
['page', 'integer', 'min' => 1, 'message' => '`页码`不合法'],
['rows', 'default', 'value' => 5],
['rows', 'integer', 'min' => 1, 'message' => '`行数`不合法'],
];
}
}
6 代码-详情接口
namespace app\controllers\actions\demo;
use app\common\base\BaseAction;
class ViewAction extends BaseAction
{
public function run() {
try {
// 模拟数据
$data = [
['id' => 1, 'name' => '赵'],
['id' => 2, 'name' => '钱'],
['id' => 3, 'name' => '孙'],
['id' => 4, 'name' => '李'],
['id' => 5, 'name' => '周'],
['id' => 6, 'name' => '吴'],
['id' => 7, 'name' => '郑'],
['id' => 8, 'name' => '王'],
['id' => 9, 'name' => '冯'],
['id' => 10, 'name' => '陈'],
['id' => 11, 'name' => '诸'],
['id' => 12, 'name' => '卫'],
['id' => 13, 'name' => '蒋'],
['id' => 14, 'name' => '沈'],
['id' => 15, 'name' => '韩'],
['id' => 16, 'name' => '杨'],
];
// 数据验证
$attrs = $this->validate()->attrs();
// 根据id查找
$detail = $attrs['id'] ? array_filter($data, function ($v) use($attrs) {
return $v['id'] == $attrs['id'];
}) : [];
return $this->success($detail ? current($detail) : []);
} catch (\Exception $e) {
return $this->error($e->getMessage());
}
}
/**
* 可接受的参数字段,rules方法中用到的字段必须先在此定义
* @return array
*/
public function attributes()
{
return ['id'];
}
/**
* 验证规则,与系统模型中的规则一样的
* @return array
*/
public function rules() {
return [
['id', 'required', 'message' => '`id`必须填写'],
['id', 'integer', 'min' => 1, 'message' => '`id`不合法'],
];
}
}
namespace app\controllers;
use yii\web\Controller;
class DemoController extends Controller
{
/**
* action映射
* @return array
*/
public function actions() {
return [
'index' => \app\controllers\actions\demo\IndexAction::class, // 列表
'view' => \app\controllers\actions\demo\ViewAction::class, // 详情
];
}
}
好了,就写这么多吧,有空再来优化一下。