<?php

/**
 * +----------------------------------------------------------------------
 * | Created By PhpStorm
 * +----------------------------------------------------------------------
 * | DATE: 2021/7/19
 * +----------------------------------------------------------------------
 * | TIME: 16:22
 * +----------------------------------------------------------------------
 * | Author: YangXing(925032253@qq.com)
 * +----------------------------------------------------------------------
 * | Class: index
 * +----------------------------------------------------------------------
 */

namespace servers;

/**
 *用法
 *
 *  $rules = [
 *       ['name|名字', 'require|email|in:7,8,9|max:10|min:6|between:6,8|length:2', '名字不能为空|名字必须必须为正确的邮件地址'],
 *       ['test|测试', 'require'],
 *   ];
 *  $data = ['name' => '8gAg:'];
 *  $validator = new validator($rules);
 *  $validator->addRule(['name|名字', 'regex', '/^[0-8|a-z]+$/', '正则验证失败哦']);  //可以为二维数组，有|的正则验证只能通过addRule。
 *  $validator->validate($data);
 *  $validator->getAllErrors(); //获取所有验证错误 array
 *  $validator->getError();  //获取第一条验证错误 string
 *  Validator::in('7,8,9', 8);  //静态调用
 *  Validator::isEmail('375373223@qq.com');
 * 表单验证器
 * Class Validator
 */
class validator
{
    /**
     * 错误信息
     * @var array
     */
    private $error = [];

    /**
     * 传入的验证规则
     * @var null
     */
    private $validate = [];

    /**
     * 需要验证的参数
     * @var array
     */
    private $data = [];

    /**
     * 添加的规则
     * @var array
     */
    private $addRules = [];

    /**
     * 默认错误提示
     * @var string[]
     */
    private $errorMsg = [
        'require' => ':attribute不能为空,is must',
        'number' => ':attribute必须为数字,must be numeric',
        'array' => ':attribute必须为数组,must be array',
        'float' => ':attribute必须为浮点数,must be float',
        'boolean' => ':attribute必须为布尔值,must be bool',
        'email' => ':attribute必须为正确的邮件地址,must be email',
        'url' => ':attribute必须为正确的url格式,must be website url',
        'ip' => ':attribute必须为正确的ip地址,must be ip',
        'timestamp' => ':attribute必须为正确的时间戳格式,must be timestamp',
        'date' => ':attribute必须为正确的日期格式,must be date',
        'regex' => ':attribute格式不正确,it is not in the correct format',
        'in' => ':attribute必须在:range内,out of range',
        'notIn' => ':attribute必须不在:range内,out of range',
        'between' => ':attribute必须在:1-:2范围内,out of range',
        'notBetween' => ':attribute必须不在:1-:2范围内,out of range',
        'max' => ':attribute最大值为(max value):1',
        'min' => ':attribute最小值为(min value):1',
        'length' => ':attribute长度必须为(length=):1',
        'maxLength' => ':attribute长度必须小于(length<):1',
        'minLength' => ':attribute长度必须大于(length>):1',
        'confirm' => ':attribute和:1不一致,not same',
        'gt' => ':attribute必须大于(must >):1',
        'lt' => ':attribute必须小于(must <):1',
        'egt' => ':attribute必须大于等于(must >=):1',
        'elt' => ':attribute必须小于等于(must <=):1',
        'eq' => ':attribute必须等于(must =):1',
    ];

    /**
     * validator constructor.
     * @param null $validate
     */
    public function __construct($validate = null)
    {
        $this->validate = $validate;
    }

    /**
     * validate 验证
     * @param $data array 需要验证的参数
     * @return bool
     */
    public function validate($data)
    {
        $this->data = $data;

        foreach ($this->validate as $item)
        {
            $item_len = count($item);
            $name = $item[0];
            $rules = $item[1];
            $rules = explode('|', $rules);
            $message = $item_len > 2 ? explode('|', $item[2]) : null;
            $this->check($name, $rules, $message);
        }

        if (!empty($this->addRules))
        {
            $this->checkAddRules();
        }

        return empty($this->error) ? true : false;
    }

    /**
     * 单个字段验证
     * @param $name
     * @param $rules
     * @param $message
     */
    private function check($name, $rules, $message)
    {

        $ruleName = $title = $name;
        if (strpos($name, '|'))
        {
            $ruleName = explode('|', $name)[0];
            $title = explode('|', $name)[1];
        }

        foreach ($rules as $i => $rule)
        {
            $validateData = isset($this->data[$ruleName]) ? $this->data[$ruleName] : null;

            $result = $this->checkResult($rule, $validateData);
            if (!$result)
            {
                $errorInfo = isset($message[$i]) ? $message[$i] : $this->getMessage($title, $rule);
                if ($errorInfo) array_push($this->error, $errorInfo);
            }
        }
    }

    /**
     * 获取验证失败的信息
     * @param $name string 字段名
     * @param $rule string 验证规则
     * @return array|false|string|string[]
     */
    private function getMessage($name, $rule)
    {
        $value1 = '';
        $value2 = '';
        $range = '';
        $errorKey = $rule;
        if (strpos($rule, ':'))
        {
            $expArr = explode(':', $rule);
            $errorKey = $expArr[0];
            $range = $expArr[1];
            $messageValue = explode(',', $expArr[1]);
            $value1 = isset($messageValue[0]) ? $messageValue[0] : '';
            $value2 = isset($messageValue[1]) ? $messageValue[1] : '';
        }

        if (isset($this->errorMsg[$errorKey]))
        {
            return str_replace([':attribute', ':range', ':1', ':2'], [$name, $range, $value1, $value2], $this->errorMsg[$errorKey]);
        }
        return false;
    }

    /**
     * 字段验证
     * @param $rule
     * @param $validateData
     * @return bool|int|mixed
     */
    private function checkResult($rule, $validateData)
    {
        switch ($rule)
        {
            case 'require':
                return $validateData != '';
                break;
            case 'number':
                return filter_var($validateData, FILTER_SANITIZE_NUMBER_INT);
                break;
            case 'array':
                return is_array($validateData);
                break;
            case 'float':
                return filter_var($validateData, FILTER_VALIDATE_FLOAT);
                break;
            case 'boolean':
                return filter_var($validateData, FILTER_VALIDATE_BOOLEAN);
                break;
            case 'email':
                return filter_var($validateData, FILTER_VALIDATE_EMAIL);
                break;
            case 'url':
                return filter_var($validateData, FILTER_SANITIZE_URL);
            case 'ip':
                return filter_var($validateData, FILTER_VALIDATE_IP);
                break;
            case 'timestamp':
                return strtotime(date('Y-m-d H:i:s', $validateData)) == $validateData;
                break;
            case 'date': //2017-11-17 12:12:12
                return strtotime($validateData);
                break;
            default:
                if (strpos($rule, ':')) {
                    $ruleArr = explode(':', $rule);
                    $funcName = substr($rule, strpos($rule, ':') + 1);
                    return call_user_func_array([$this, $ruleArr[0]], [$funcName, $validateData]);
                } else {
                    return call_user_func_array([$this, $rule], [$rule, $validateData]);
                }
                break;
        }
    }

    /**
     * 正则验证
     * @param $rule
     * @param $data
     * @return mixed
     */
    public static function regex($rule, $data)
    {
        return filter_var($data, FILTER_VALIDATE_REGEXP, ["options" => ["regexp" => $rule]]);
    }

    /**
     * 添加规则格式
     * @param $rule
     */
    public function addRule($rule)
    {
        is_array(current($rule)) ? $this->addRules = array_merge($this->addRules, $rule) : array_push($this->addRules, $rule);
    }

    //添加新的规则的验证
    public function checkAddRules()
    {

        foreach ($this->addRules as $item)
        {
            $name = $item[0];

            $message = isset($item[3]) ? $item[3] : null;

            $ruleName = $title = $name;

            if (strpos($name, '|'))
            {
                $ruleName = explode('|', $name)[0];
                $title = explode('|', $name)[1];
            }

            $validateData = isset($this->data[$ruleName]) ? $this->data[$ruleName] : null;

            $result = $this->checkResult($item[1] . ':' . $item[2], $validateData);

            if (!$result)
            {
                $errorInfo = isset($message) ? $message : $this->getMessage($title, $item[1]);
                if ($errorInfo) array_push($this->error, $errorInfo);
            }
        }
    }

    /**
     * @param $rule
     * @param $data
     * @return bool
     */
    public static function in($rule, $data)
    {
        if (!is_array($rule))  $rule = explode(',', $rule);
        return in_array($data, $rule);
    }

    /**
     * @param $rule
     * @param $data
     * @return bool
     */
    public static function notIn($rule, $data)
    {
        return !self::in($data, $rule);
    }

    /**
     * @param $rule
     * @param $data
     * @return bool
     */
    public static function between($rule, $data)
    {
        $rule = explode(',', $rule);
        return $data >= $rule[0] && $data <= $rule[1];
    }

    /**
     * @param $rule
     * @param $data
     * @return bool
     */
    public static function notBetween($rule, $data)
    {
        return !self::between($rule, $data);
    }

    /**
     * @param $rule
     * @param $data
     * @return bool
     */
    public static function max($rule, $data)
    {
        return $data <= $rule;
    }

    /**
     * @param $rule
     * @param $data
     * @return bool
     */
    public static function min($rule, $data)
    {
        return $data >= $rule;
    }

    /**
     * @param $rule
     * @param $data
     * @return bool
     */
    public static function length($rule, $data)
    {
        $length = is_array($data) ? count($data) : iconv_strlen($data);
        return $length == $rule;
    }

    /**
     * 最大长度
     * @param $rule
     * @param $data
     * @return bool
     */
    public static function maxLength($rule, $data)
    {
        $length = is_array($data) ? count($data) : iconv_strlen($data);
        return $length <= $rule;
    }

    /**
     * 最小长度
     * @param $rule
     * @param $data
     * @return bool
     */
    public static function minLength($rule, $data)
    {
        $length = is_array($data) ? count($data) : iconv_strlen($data);
        return $length >= $rule;
    }

    /**
     * @param $rule
     * @param $data
     * @return bool
     */
    public static function confirm($rule, $data)
    {
        return isset(self::$data[$rule]) && $data == self::$data[$rule];
    }

    /**
     * @param $rule
     * @param $data
     * @return bool
     */
    public static function gt($rule, $data)
    {
        return $data > $rule;
    }

    /**
     * @param $rule
     * @param $data
     * @return bool
     */
    public static function lt($rule, $data)
    {
        return $data < $rule;
    }

    /**
     * @param $rule
     * @param $data
     * @return bool
     */
    public static function egt($rule, $data)
    {
        return $data >= $rule;
    }

    /**
     * @param $rule
     * @param $data
     * @return bool
     */
    public static function elt($rule, $data)
    {
        return $data <= $rule;
    }

    /**
     * @param $rule
     * @param $data
     * @return bool
     */
    public static function eq($rule, $data)
    {
        return $data == $rule;
    }

    /**
     * 获取验证失败的第一条信息
     * @return mixed|null
     */
    public function getError()
    {

        return count($this->error) > 0 ? $this->error[0] : null;
    }

    /**
     * 获取所有验证失败的信息
     * @return array
     */
    public function getAllErrors()
    {
        return $this->error;
    }

    /**
     * 调用自定义函数或者
     * @param $func
     * @param $data
     * @return false|mixed
     */
    function __call($func, $data)
    {
        $func_arr = get_defined_functions();
        if (in_array($func, $func_arr['user'])) {
            return call_user_func($func, $data);
        } else {
            array_push($this->error, '没有' . $func . '这个方法');
        }
    }

    /**
     * 静态方法调用自定义函数或者
     * @param $func
     * @param $data
     * @return false|mixed
     */
    public static function __callStatic($func, $data)
    {
        if (substr($func, 0, 2) == 'is')
        {
            return call_user_func_array([new self, 'checkResult'], [strtolower(substr($func, 2)), $data[0]]);
        }
    }
}
