自己实现一个简易的 validator

本文发表同步发表在我的blog: https://www.h57.pw/2019/05/03/implement-a-...

最近在做一些自己的小东西,因为没用 laravel,所以对感觉很多东西都没有 laravel 那么顺手,很多东西都得自己搞定才行,不过也正是因为这样,很多东西弄起来,更符合自己的需求了。

以前写表单请求的时候没感觉一个验证器会让我写代码的时候轻松一些,但是因为最近写的表单更多,我越来越发现一个验证器会让我少些多少代码,所以,我自己实现了一个简易的验证器。

<?php

namespace App\Validator;

use Slim\Http\Request;

/**
 * 验证器的根类
 * Class AbstractValidator
 * @package App\Validator
 */
abstract class AbstractValidator
{
    /**
     * @var Request
     */
    protected $request;
    /**
     * @var array
     */
    protected $extraParams;

    /**
     * 保存条件的数组
     * @var array
     * rules 的 key 是验证的变量名称
     * rules 的 value 是个数组
     * value 可包含 type type是声明变量的类型,可包含 string | integer | float | double | array | regex 其他的需要用到的时候在添加
     *       不同的类型会依赖后续不同的参数
     * value 包含 require 这个类型是 true | false, 如果是 true 就是必填项,如果请求参数不包含这个就会返回 false,默认 false
     * value 可包含 message 出错的提示信息,如果出错没有 message 的时候就考大家在 validation 里面自己实现默认的消息了,
     *       如果包含 type 键,这个 message 就是 type 出错的 message,如果没有 type 这个就可以是 require 为 true 的错误消息
     * value 可包含 requireMessage 这个是 require 为 true 时候的出错消息,如果这个没有,就返回默认的信息 'xxx必须填写'
     */
    abstract public function rules();

    public function validation($request, $extraParams)
    {
        $this->request = $request;
        $this->extraParams = $extraParams;

        return $this->doValidation();
    }

    /**
     * 执行验证流程
     */
    public function doValidation()
    {
        /**
         * @var array $rules
         */
        $rules = $this->rules();

        if (!is_array($rules)) {
            return true;
        }

        // 获取请求方法
        $requestMethod = $this->request->getMethod();
        // 获取参数,如果是get就直接获取get参数,如果是其他请求方法
        // 则优先获取post参数,如果不存在在获取get参数,如果依然不存在
        // 就获取 extraParams
        // 每个字段的rules 也改变为数组就ok了
        foreach ($rules as $paramName => $fieldRules) {

            $value = $this->request->getParsedBodyParam($paramName);

            if (empty($value) && strtoupper($requestMethod) !== 'GET') {
                $value = $this->request->getQueryParam($paramName);
            }

            if (array_key_exists($paramName, $this->extraParams)) {
                $value = $this->extraParams[$paramName];
            }

            $fieldType = 'string';
            foreach ($fieldRules as $rule) {
                if ($rule['type'] !== 'require' && empty($value)) {
                    continue;
                }
                switch ($rule['type']) {
                    case 'require':
                        if (empty($value)) {
                            return [false, $rule['message'] ?: $paramName . '必须存在'];
                        }
                        break;
                    case 'string':
                        break;
                    case 'integer':
                        if (!is_numeric($value) || ($value + 0) !== intval($value)) {
                            return [false, $rule['message'] ?: ''];
                        }

                        $value = intval($value);
                        $fieldType = 'integer';
                        break;
                    case 'float':
                    case 'double':
                        if (!is_numeric($value) || ($value + 0) !== floatval($value)) {
                            return [false, $rule['message'] ?: ''];
                        }
                        $value = floatval($value);
                        $fieldType = 'double';
                        break;
                    case 'regex':
                        // 正则
                        if (!preg_match('~' . $rule['pattern'] . '~iu', $value)) {
                            return [false, $rule['message'] ?: ''];
                        }
                        break;
                    case 'array':
                        // 数组
                        if (!is_array($value)) {
                            return [false, $rule['message'] ?: ''];
                        }
                        $fieldType = 'array';
                        break;
                    case 'min':
                        switch ($fieldType) {
                            case 'string':
                                if (mb_strlen($value) < $rule['value']) {
                                    return [false, $rule['message'] ?: ''];
                                }
                                break;
                            case 'integer':
                            case 'double':
                                if ($value < $rule['value']) {
                                    return [false, $rule['message'] ?: ''];
                                }
                                break;
                            case 'array':
                                if (count($value) < $rule['value']) {
                                    return [false, $rule['message'] ?: ''];
                                }
                                break;
                        }
                        break;
                    case 'max':
                        switch ($fieldType) {
                            case 'string':
                                if (mb_strlen($value) > $rule['value']) {
                                    return [false, $rule['message'] ?: ''];
                                }
                                break;
                            case 'integer':
                            case 'double':
                                if ($value > $rule['value']) {
                                    return [false, $rule['message'] ?: ''];
                                }
                                break;
                            case 'array':
                                if (count($value) > $rule['value']) {
                                    return [false, $rule['message'] ?: ''];
                                }
                                break;
                        }
                        break;
                    case 'length':
                        switch ($fieldType) {
                            case 'string':
                                if (mb_strlen($value) !== $rule['value']) {
                                    return [false, $rule['message'] ?: ''];
                                }
                                break;
                            case 'integer':
                            case 'double':
                                if ($value !== $rule['value']) {
                                    return [false, $rule['message'] ?: ''];
                                }
                                break;
                            case 'array':
                                if (count($value) !== $rule['value']) {
                                    return [false, $rule['message'] . count($value) ?: ''];
                                }
                                break;
                        }
                        break;
                    case 'list':
                        if (!in_array($value, $rule['value'], true)) {
                            return [false, $rule['message'] ?: ''];
                        }
                        break;
                }
            }
        }

        return [true, ''];
    }
}

这次写代码用的是 slim 框架,所以一部分的类依赖我就使用了 slim 的相关类。弄好了这个之后写表单使用起来就简易多了,写一个类继承这个 AbstractValidator 类,只需实现一个方法 rules, 这个方法返回一个数组,一个需要验证的表单数组就ok了。

在验证的地方,调用 validation 方法就可以了,在返回失败的时候实现自己的返回失败逻辑就OK了。实例如下

<?php

namespace App\Validator;

class TestValidator extends AbstractValidator
{

    /**
     * 保存条件的数组
     * @var array
     * rules 的 key 是验证的变量名称
     * rules 的 value 是个数组
     * value 可包含 type type是声明变量的类型,可包含 string | integer | float | double | array | regex 其他的需要用到的时候在添加
     *       不同的类型会依赖后续不同的参数
     * value 包含 require 这个类型是 true | false, 如果是 true 就是必填项,如果请求参数不包含这个就会返回 false,默认 false
     * value 可包含 message 出错的提示信息,如果出错没有 message 的时候就考大家在 validation 里面自己实现默认的消息了,
     *       如果包含 type 键,这个 message 就是 type 出错的 message,如果没有 type 这个就可以是 require 为 true 的错误消息
     * value 可包含 requireMessage 这个是 require 为 true 时候的出错消息,如果这个没有,就返回默认的信息 'xxx必须填写'
     */
    public function rules()
    {
        // TODO: Implement rules() method.
        return [
            'field1' => [
                ['type' => 'require'],
                ['type' => 'integer', 'message' => '必须是个数字'],
                ['type' => 'max', 'value' => 999, 'message' => '最大只能是999'],
                ['type' => 'min', 'value' => 100, 'message' => '最小只能是100'],
            ],
            'field2' => [
                ['type' => 'regex', 'pattern' => 'a[\d]+','message' => 'field2xxs'],
            ],
        ];
    }
}

上面这个类就是我们自己的 validator, 下面就是我们的验证逻辑部分, 在 slim 中 只要实现一个 中间件就可以了,如下

<?php
/**
 * Created by PhpStorm.
 * User: haoliang
 * Date: 2019-03-13
 * Time: 15:20
 */

namespace App\Middleware;

use App\Controller\User;
use App\Utils\JWT;
use App\Validator\AbstractValidator;
use Jose\Component\Core\Converter\StandardConverter;
use Slim\Container;
use Slim\Http\Request;
use Slim\Http\Response;
use App\Model\User as UserModel;

/**
 * 检测菜单权限的中间件
 * Class CheckUrl
 * @package App\Middleware
 */
class ValidateMiddleware
{
    /**
     * @var AbstractValidator $validator ;
     */
    private $validator;
    /**
     * @var Container $container ;
     */
    private $container;

    public function __construct(Container $container, AbstractValidator $validator)
    {
        $this->container = $container;
        $this->validator = $validator;
    }

    /**
     * @param Request $request
     * @param Response $response
     * @param $next
     * @return Response
     */
    public function __invoke(Request $request, Response $response, $next)
    {
        $routeParams = $request->getAttribute('routeInfo')[2];
        $this->container->get('logger')->info(json_encode($routeParams));

        list($result, $message) = $this->validator->validation($request, $routeParams);
        $this->container->get('logger')->info($result . '   '. $message);

        if ($result) {
            $response = $next($request, $response);
        } else {
            $response = $response->withJson([
                'status' => -19999,
                'message' => $message,
                'data' => '',
            ]);
        }

        return $response;
    }
}

在 slim 中,就是在 __invoke 方法中实现验证逻辑就ok了。

这次实现的比较简单,我自己都觉得还有很大的优化余地,慢慢优化ing。

讨论数量: 4

我在想一个问题 422拦不住用户吗?即使拦不住 前端不能控制下表单的输入吗

4年前 评论

@罪人 422 是什么?只靠前端控制是不够的啊,在进入数据前的最后一次防线肯定要是后端做的啊

4年前 评论

422是request的验证 在Http目录下的Requests文件下写验证 然后如果靠前端来控制选择框select或者文本框是能够办到的把 我猜 然后在进行数据强类型应该就能满足了

4年前 评论

@罪人 看清文章,这个验证器并不是针对laravel的,至于你说的422,我依然不知道是什么,而且,前端数据永远不可信,比如一个checkbox,有 0 和1 两个值,那么如果前端传过来一个2 怎么办,所以后端验证是必须存在的,写这个验证器的目的仅仅是为了让代码更简练一些,这个验证器的最终目的,是跟laravel 的 request 中的 rules() 方法一样的

4年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!