├── .github ├── changelog.yml ├── dependabot.yml └── workflows │ ├── php.yml │ └── release.yml ├── .gitignore ├── .nojekyll ├── .php_cs ├── LICENSE ├── README.en.md ├── README.md ├── _navbar.md ├── composer.json ├── docs ├── api.md ├── help.md └── idea.md ├── index.html ├── phpunit.xml ├── src ├── AbstractValidation.php ├── Exception │ ├── ArrayValueNotExists.php │ └── ValidateException.php ├── FV.php ├── FieldValidation.php ├── Filter │ ├── FilteringTrait.php │ ├── Filters.php │ ├── Filtration.php │ └── UserFilters.php ├── GlobalOption.php ├── Helper.php ├── Locale │ └── LocaleZhCN.php ├── RV.php ├── RuleValidation.php ├── Simple │ ├── VData.php │ ├── ValidData.php │ └── ValidValue.php ├── Traits │ ├── ErrorMessageTrait.php │ ├── MultipleRulesTrait.php │ ├── NameAliasTrait.php │ └── ScopedValidatorsTrait.php ├── Validation.php ├── ValidationInterface.php ├── ValidationTrait.php ├── Validator │ ├── AbstractValidator.php │ ├── GlobalMessage.php │ ├── UserValidators.php │ └── ValidatorInterface.php └── Validators.php └── test ├── BaseValidateTestCase.php ├── Example ├── DataModel.php ├── FieldExample.php ├── PageRequest.php └── RuleExample.php ├── FieldValidationTest.php ├── Filter ├── FiltersTest.php ├── FiltrationTest.php └── UserFiltersTest.php ├── FiltrationTest.php ├── HelperTest.php ├── IssuesTest.php ├── RuleValidationTest.php ├── Simple └── ValidDataTest.php ├── Traits ├── ErrorMessageTraitTest.php └── ScopedValidatorsTest.php ├── ValidationTraitTest.php ├── Validator ├── AdemoValidator.php ├── GlobalMessageTest.php ├── Rule.php └── UserValidatorsTest.php ├── ValidatorsTest.php ├── avatar.jpeg └── bootstrap.php /.github/changelog.yml: -------------------------------------------------------------------------------- 1 | title: '## Change Log' 2 | # style allow: simple, markdown(mkdown), ghr(gh-release) 3 | style: gh-release 4 | # group names 5 | names: [Refactor, Fixed, Feature, Update, Other] 6 | #repo_url: https://github.com/gookit/gcli 7 | 8 | filters: 9 | # message length should >= 12 10 | - name: msg_len 11 | min_len: 12 12 | # message words should >= 3 13 | - name: words_len 14 | min_len: 3 15 | - name: keyword 16 | keyword: format code 17 | exclude: true 18 | - name: keywords 19 | keywords: format code, action test 20 | exclude: true 21 | 22 | # group match rules 23 | # not matched will use 'Other' group. 24 | rules: 25 | - name: Refactor 26 | start_withs: [refactor, break] 27 | contains: ['refactor:'] 28 | - name: Fixed 29 | start_withs: [fix] 30 | contains: ['fix:'] 31 | - name: Feature 32 | start_withs: [feat, new] 33 | contains: [feature] 34 | - name: Update 35 | start_withs: [update, 'up:'] 36 | contains: [] 37 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: composer 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | open-pull-requests-limit: 10 8 | 9 | - package-ecosystem: "github-actions" 10 | directory: "/" 11 | schedule: 12 | # Check for updates to GitHub Actions every weekday 13 | interval: "daily" 14 | -------------------------------------------------------------------------------- /.github/workflows/php.yml: -------------------------------------------------------------------------------- 1 | name: Unit-tests 2 | 3 | on: 4 | push: 5 | paths: 6 | - '**.php' 7 | - 'composer.json' 8 | - '**.yml' 9 | # Trigger the workflow on any pull request 10 | pull_request: 11 | 12 | jobs: 13 | test: 14 | name: Test on php ${{ matrix.php}} 15 | runs-on: ubuntu-latest 16 | timeout-minutes: 10 17 | strategy: 18 | fail-fast: true 19 | matrix: 20 | php: [ 8.1, 8.2, 8.3, 8.4] # 7.1, 7.2, 21 | 22 | steps: 23 | - name: Checkout 24 | uses: actions/checkout@v4 25 | 26 | # usage refer https://github.com/shivammathur/setup-php 27 | - name: Setup PHP 28 | timeout-minutes: 5 29 | uses: shivammathur/setup-php@v2 30 | with: 31 | php-version: ${{ matrix.php}} 32 | tools: pecl, php-cs-fixer, phpunit:${{ matrix.phpunit }} 33 | extensions: mbstring, dom, fileinfo, mysql, openssl, igbinary, redis # , swoole-4.4.19 #optional, setup extensions 34 | ini-values: post_max_size=56M, short_open_tag=On #optional, setup php.ini configuration 35 | coverage: none #optional, setup coverage driver: xdebug, none 36 | 37 | - name: Display Env 38 | run: env 39 | 40 | - name: Install dependencies 41 | run: composer install --no-progress 42 | 43 | # Add a test script to composer.json, for instance: "test": "vendor/bin/phpunit" 44 | # Docs: https://getcomposer.org/doc/articles/scripts.md 45 | 46 | - name: Run unit tests 47 | # run: composer run test 48 | run: phpunit 49 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Tag-release 2 | 3 | on: 4 | push: 5 | tags: 6 | - v* 7 | 8 | jobs: 9 | release: 10 | name: New tag release 11 | runs-on: ubuntu-latest 12 | timeout-minutes: 10 13 | strategy: 14 | fail-fast: true 15 | 16 | steps: 17 | - name: Checkout 18 | uses: actions/checkout@v4 19 | with: 20 | fetch-depth: 0 21 | 22 | - name: Set ENV for github-release 23 | # https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-commands-for-github-actions#setting-an-environment-variable 24 | run: | 25 | echo "RELEASE_TAG=${GITHUB_REF:10}" >> $GITHUB_ENV 26 | echo "RELEASE_NAME=$GITHUB_WORKFLOW" >> $GITHUB_ENV 27 | 28 | - name: Generate changelog 29 | run: | 30 | curl https://github.com/gookit/gitw/releases/latest/download/chlog-linux-amd64 -L -o /usr/local/bin/chlog 31 | chmod a+x /usr/local/bin/chlog 32 | chlog -c .github/changelog.yml -o changelog.md prev last 33 | 34 | # https://github.com/softprops/action-gh-release 35 | - name: Create release and upload assets 36 | uses: softprops/action-gh-release@v2 37 | env: 38 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 39 | with: 40 | name: ${{ env.RELEASE_TAG }} 41 | tag_name: ${{ env.RELEASE_TAG }} 42 | body_path: changelog.md 43 | token: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | ### OSX template 3 | *.DS_Store 4 | 5 | .idea/ 6 | .phpintel/ 7 | /vendor/ 8 | !README.md 9 | !.gitkeep 10 | composer.lock 11 | *.swp 12 | *.swo 13 | .DS_Store 14 | .phpunit.result.cache 15 | *.cache -------------------------------------------------------------------------------- /.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inhere/php-validate/7db74fec9cf4fa041544e8569ee874e8529e4ad1/.nojekyll -------------------------------------------------------------------------------- /.php_cs: -------------------------------------------------------------------------------- 1 | setRiskyAllowed(true)->setRules([ 9 | '@PSR2' => true, 10 | // 'header_comment' => [ 11 | // 'comment_type' => 'PHPDoc', 12 | // 'header' => $header, 13 | // 'separate' => 'none' 14 | // ], 15 | 'array_syntax' => [ 16 | 'syntax' => 'short' 17 | ], 18 | 'single_quote' => true, 19 | 'class_attributes_separation' => true, 20 | 'no_unused_imports' => true, 21 | 'standardize_not_equals' => true, 22 | 'declare_strict_types' => true, 23 | ])->setFinder(PhpCsFixer\Finder::create() 24 | // ->exclude('test') 25 | ->exclude('docs')->exclude('vendor')->in(__DIR__))->setUsingCache(false); 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 inhere 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.en.md: -------------------------------------------------------------------------------- 1 | # PHP Validate 2 | 3 | [](LICENSE) 4 | [](https://packagist.org/packages/inhere/php-validate) 5 | [](https://packagist.org/packages/inhere/php-validate) 6 | [](https://coveralls.io/github/inhere/php-validate?branch=master) 7 | [](https://github.com/inhere/php-validate/actions) 8 | [](README.md) 9 | 10 | Lightweight and feature-rich PHP validation and filtering library. 11 | 12 | - Simple and convenient, support to add custom validator 13 | - Support pre-verification check, customize how to judge non-empty 14 | - Support grouping rules by scene. Or partial verification 15 | - Supports the use of filters to purify and filter values before verification [built-in filter](#built-in-filters) 16 | - Support pre-processing and post-processing of verification [independent verification processing](#on-in-Validation) 17 | - Support to customize the error message, field translation, message translation of each verification, and support the default value 18 | - Supports basic array checking, checking of children (`'goods.apple'`) values of arrays, checking of children of wildcards (`'users.*.id' 'goods.*'`) 19 | - Easy access to error information and secure data after verification (only data that has been checked regularly) 20 | - More than 60 commonly used validators have been built-in [built-in validator](#built-in-validators) 21 | - Rule setting reference `yii`, `laravel`, `Respect/Validation` 22 | - Independent filter `Inhere\Validate\Filter\Filtration`, can be used for data filtering alone 23 | 24 | ## License 25 | 26 | [MIT](LICENSE) 27 | -------------------------------------------------------------------------------- /_navbar.md: -------------------------------------------------------------------------------- 1 | 2 | 我的其他项目 3 | 4 | * [EasyTpl](https://phppkg.github.io/easytpl/ "template engine") 5 | * [pflag](https://php-toolkit.github.io/pflag/ "console option and argument parse") 6 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "inhere/php-validate", 3 | "type": "library", 4 | "description": "generic data validate, filter library of the php", 5 | "keywords": [ 6 | "php-library", 7 | "library", 8 | "validate", 9 | "form-validate", 10 | "validation" 11 | ], 12 | "homepage": "https://github.com/inhere/php-validate", 13 | "license": "MIT", 14 | "authors": [ 15 | { 16 | "name": "inhere", 17 | "email": "in.798@qq.com", 18 | "homepage": "https://github.com/inhere" 19 | } 20 | ], 21 | "require": { 22 | "php": ">8.1.0", 23 | "toolkit/stdlib": "~2.0" 24 | }, 25 | "require-dev": {}, 26 | "autoload": { 27 | "psr-4": { 28 | "Inhere\\Validate\\": "src/" 29 | } 30 | }, 31 | "autoload-dev": { 32 | "psr-4": { 33 | "Inhere\\ValidateTest\\": "test/" 34 | } 35 | }, 36 | "scripts": { 37 | "check-cs": "./php-cs-fixer fix --dry-run --diff --diff-format=udiff", 38 | "cs-fix": "./php-cs-fixer fix", 39 | "test": "vendor/bin/phpunit" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /docs/api.md: -------------------------------------------------------------------------------- 1 | # methods API 2 | -------------------------------------------------------------------------------- /docs/help.md: -------------------------------------------------------------------------------- 1 | # help 2 | 3 | 4 | ```php 5 | 6 | /** 7 | * @link http://php.net/manual/zh/function.filter-input.php 8 | * @param int $type INPUT_GET, INPUT_POST, INPUT_COOKIE, INPUT_SERVER, or INPUT_ENV 9 | * @param $varName 10 | * @param array $filter 过滤/验证器 {@link http://php.net/manual/zh/filter.filters.php} 11 | * @param array $options 一个选项的关联数组,或者按位区分的标示。 12 | * 如果过滤器接受选项,可以通过数组的 "flags" 位去提供这些标示。 13 | * 如果成功的话返回所请求的变量。 14 | * 如果成功的话返回所请求的变量。 15 | * 如果过滤失败则返回 FALSE , 16 | * 如果 varName 不存在的话则返回 NULL 。 17 | * 如果标示 FILTER_NULL_ON_FAILURE 被使用了,那么当变量不存在时返回 FALSE ,当过滤失败时返回 NULL 。 18 | */ 19 | public static function input($type, $varName, $filter, array $options = []) 20 | { 21 | } 22 | 23 | public static function multi(array $data, array $filters = []) 24 | { 25 | } 26 | 27 | /** 28 | * @link http://php.net/manual/zh/function.filter-input-array.php 29 | * 检查(验证/过滤)输入数据中的多个变量名 like filter_input_array() 30 | * 当需要获取很多变量却不想重复调用 filter_input()时很有用。 31 | * @param int $type One of INPUT_GET, INPUT_POST, INPUT_COOKIE, INPUT_SERVER, or INPUT_ENV. 要检查的输入数据 32 | * @param mixed $definition 一个定义参数的数组。 33 | * 一个有效的键必须是一个包含变量名的string, 34 | * 一个有效的值要么是一个filter type,或者是一个array 指明了过滤器、标示和选项。 35 | * 如果值是一个数组,那么它的有效的键可以是 : 36 | * filter, 用于指明 filter type, 37 | * flags 用于指明任何想要用于过滤器的标示, 38 | * options 用于指明任何想要用于过滤器的选项。 39 | * 参考下面的例子来更好的理解这段说明。 40 | * @param bool $addEmpty 在返回值中添加 NULL 作为不存在的键。 41 | * 如果成功的话返回一个所请求的变量的数组, 42 | * 如果失败的话返回 FALSE 。 43 | * 对于数组的值, 44 | * 如果过滤失败则返回 FALSE , 45 | * 如果 variable_name 不存在的话则返回 NULL 。 46 | * 如果标示 FILTER_NULL_ON_FAILURE 被使用了,那么当变量不存在时返回 FALSE ,当过滤失败时返回 NULL 。 47 | */ 48 | public static function inputMulti($type, $definition, $addEmpty = true) 49 | { 50 | } 51 | 52 | /** 53 | * 检查变量名是否存在 54 | * @param int $type One of INPUT_GET, INPUT_POST, INPUT_COOKIE, INPUT_SERVER, or INPUT_ENV. 要检查的输入数据 55 | * @param string $varName Name of a variable to check. 要检查的变量名 56 | */ 57 | public static function inputHasVar($type, $varName) 58 | { 59 | } 60 | ``` -------------------------------------------------------------------------------- /docs/idea.md: -------------------------------------------------------------------------------- 1 | # Idea 2 | 3 | ## ruleEngine 4 | 5 | - string rules. like Laravel 6 | - array rules. like Yii 7 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 | 7 | 8 |' 允许 和
520 | *
521 | * @return string
522 | */
523 | public static function stripTags(mixed $val, ?string $allowedTags = null): string
524 | {
525 | if (!$val || !is_string($val)) {
526 | return '';
527 | }
528 |
529 | return $allowedTags ? strip_tags($val, $allowedTags) : strip_tags($val);
530 | }
531 |
532 | /**
533 | * 去除 URL 编码不需要的字符。
534 | *
535 | * @note 与 urlencode() 函数很类似。
536 | *
537 | * @param string $val 要过滤的数据
538 | * @param int $flags 标志
539 | * FILTER_FLAG_STRIP_LOW - 去除 ASCII 值在 32 以下的字符
540 | * FILTER_FLAG_STRIP_HIGH - 去除 ASCII 值在 32 以上的字符
541 | * FILTER_FLAG_ENCODE_LOW - 编码 ASCII 值在 32 以下的字符
542 | * FILTER_FLAG_ENCODE_HIGH - 编码 ASCII 值在 32 以上的字符
543 | *
544 | * @return string
545 | */
546 | public static function encoded(string $val, int $flags = 0): string
547 | {
548 | $settings = $flags !== 0 ? ['flags' => $flags] : [];
549 |
550 | return (string)filter_var($val, FILTER_SANITIZE_ENCODED, $settings);
551 | }
552 |
553 | /**
554 | * 应用 addslashes() 转义数据
555 | *
556 | * @param string $val
557 | *
558 | * @return string
559 | */
560 | public static function quotes(string $val): string
561 | {
562 | $flag = FILTER_SANITIZE_ADD_SLASHES;
563 |
564 | return (string)filter_var($val, $flag);
565 | }
566 |
567 | /**
568 | * like htmlspecialchars(), HTML 转义字符 '"<>& 以及 ASCII 值小于 32 的字符。
569 | *
570 | * @param string $val
571 | * @param int $flags 标志
572 | * FILTER_FLAG_STRIP_LOW - 去除 ASCII 值在 32 以下的字符
573 | * FILTER_FLAG_STRIP_HIGH - 去除 ASCII 值在 32 以上的字符
574 | * FILTER_FLAG_ENCODE_HIGH - 编码 ASCII 值在 32 以上的字符
575 | *
576 | * @return string
577 | */
578 | public static function specialChars(string $val, int $flags = 0): string
579 | {
580 | $settings = $flags !== 0 ? ['flags' => $flags] : [];
581 |
582 | return (string)filter_var($val, FILTER_SANITIZE_SPECIAL_CHARS, $settings);
583 | }
584 |
585 | /**
586 | * @param string $val
587 | * @param int $flags
588 | *
589 | * @return string
590 | */
591 | public static function escape(string $val, int $flags = 0): string
592 | {
593 | return self::specialChars($val, $flags);
594 | }
595 |
596 | /**
597 | * HTML 转义字符 '"<>& 以及 ASCII 值小于 32 的字符。
598 | *
599 | * @param string $val
600 | * @param int $flags 标志 FILTER_FLAG_NO_ENCODE_QUOTES
601 | *
602 | * @return string
603 | */
604 | public static function fullSpecialChars(string $val, int $flags = 0): string
605 | {
606 | $settings = $flags !== 0 ? ['flags' => $flags] : [];
607 |
608 | return (string)filter_var($val, FILTER_SANITIZE_FULL_SPECIAL_CHARS, $settings);
609 | }
610 |
611 | /**
612 | * 字符串长度过滤截取
613 | *
614 | * @param string $string
615 | * @param int|string $start
616 | * @param int|string $length
617 | *
618 | * @return string
619 | */
620 | public static function stringCute(string $string, int|string $start = 0, int|string $length = 0): string
621 | {
622 | return self::subStr($string, (int)$start, (int)$length);
623 | }
624 |
625 | /**
626 | * @param string $string
627 | * @param int|string $start
628 | * @param int|string $length
629 | *
630 | * @return string
631 | */
632 | public static function cut(string $string, int|string $start = 0, int|string $length = 0): string
633 | {
634 | return self::stringCute($string, $start, $length);
635 | }
636 |
637 | /**
638 | * url地址过滤 移除所有不符合 url 的字符
639 | *
640 | * - 该过滤器允许所有的字母、数字以及 $-_.+!*'(),{}|\^~[]`"><#%;/?:@&=
641 | *
642 | * @param string $val 要过滤的数据
643 | *
644 | * @return string
645 | */
646 | public static function url(string $val): string
647 | {
648 | return (string)filter_var($val, FILTER_SANITIZE_URL);
649 | }
650 |
651 | /**
652 | * email 地址过滤 移除所有不符合 email 的字符
653 | *
654 | * @param string $val 要过滤的数据
655 | *
656 | * @return string
657 | */
658 | public static function email(string $val): string
659 | {
660 | return (string)filter_var($val, FILTER_SANITIZE_EMAIL);
661 | }
662 |
663 | /**
664 | * 不进行任何过滤,去除或编码特殊字符。这个过滤器也是FILTER_DEFAULT别名。
665 | * 该过滤器删除那些对应用程序有潜在危害的数据。它用于去除标签以及删除或编码不需要的字符。
666 | * 如果不规定标志,则该过滤器没有任何行为。
667 | *
668 | * @param string $string
669 | * @param int|string $flags 标志
670 | * FILTER_FLAG_STRIP_LOW - 去除 ASCII 值在 32 以下的字符
671 | * FILTER_FLAG_STRIP_HIGH - 去除 ASCII 值在 32 以上的字符
672 | * FILTER_FLAG_ENCODE_LOW - 编码 ASCII 值在 32 以下的字符
673 | * FILTER_FLAG_ENCODE_HIGH - 编码 ASCII 值在 32 以上的字符
674 | * FILTER_FLAG_ENCODE_AMP - 把 & 字符编码为 &
675 | *
676 | * @return string|mixed
677 | */
678 | public static function unsafeRaw(string $string, int|string $flags = 0): mixed
679 | {
680 | $settings = (int)$flags !== 0 ? ['flags' => (int)$flags] : [];
681 |
682 | return filter_var($string, FILTER_UNSAFE_RAW, $settings);
683 | }
684 |
685 | /**
686 | * 自定义回调过滤
687 | *
688 | * @param mixed $val
689 | * @param callable $callback
690 | *
691 | * @return bool|mixed
692 | */
693 | public static function callback(mixed $val, callable $callback): mixed
694 | {
695 | return filter_var($val, FILTER_CALLBACK, ['options' => $callback]);
696 | }
697 |
698 | /**
699 | * 去除数组中的重复值
700 | *
701 | * @param mixed $val
702 | *
703 | * @return array
704 | */
705 | public static function unique(mixed $val): array
706 | {
707 | if (!$val || !is_array($val)) {
708 | return (array)$val;
709 | }
710 |
711 | return array_unique($val);
712 | }
713 | }
714 |
--------------------------------------------------------------------------------
/src/Filter/Filtration.php:
--------------------------------------------------------------------------------
1 | 'tom'],
23 | * ['email', 'string|email'],
24 | * ])->filtering();
25 | */
26 | class Filtration
27 | {
28 | use FilteringTrait;
29 |
30 | /** @var array raw data */
31 | private array $_data;
32 |
33 | /** @var array the rules is by setRules() */
34 | private array $_rules;
35 |
36 | /**
37 | * @param array $data
38 | * @param array $rules
39 | *
40 | * @return Filtration
41 | */
42 | public static function make(array $data = [], array $rules = []): self
43 | {
44 | return new self($data, $rules);
45 | }
46 |
47 | /**
48 | * Filtration constructor.
49 | *
50 | * @param array $data
51 | * @param array $rules
52 | */
53 | public function __construct(array $data = [], array $rules = [])
54 | {
55 | $this->_data = $data;
56 | $this->_rules = $rules;
57 | }
58 |
59 | /**
60 | * @param array $data
61 | *
62 | * @return $this
63 | */
64 | public function load(array $data): self
65 | {
66 | $this->_data = array_merge($this->_data, $data);
67 | return $this;
68 | }
69 |
70 | /**
71 | * @param array $rules
72 | *
73 | * @return array
74 | */
75 | public function filtering(array $rules = []): array
76 | {
77 | return $this->applyRules($rules);
78 | }
79 |
80 | /**
81 | * Apply a series of filtering rules to the input data
82 | *
83 | * @param array $rules
84 | * @param array $data
85 | *
86 | * @return array Return filtered data
87 | */
88 | public function applyRules(array $rules = [], array $data = []): array
89 | {
90 | $data = $data ?: $this->_data;
91 | $rules = $rules ?: $this->_rules;
92 | // save clean data
93 | $filtered = [];
94 |
95 | foreach ($rules as $rule) {
96 | if (!isset($rule[0], $rule[1])) {
97 | continue;
98 | }
99 |
100 | if (!$fields = $rule[0]) {
101 | continue;
102 | }
103 |
104 | $fields = is_string($fields) ? Filters::explode($fields) : (array)$fields;
105 |
106 | foreach ($fields as $field) {
107 | if (!isset($data[$field]) && isset($rule['default'])) {
108 | $filtered[$field] = $rule['default'];
109 | } else {
110 | $filtered[$field] = $this->valueFiltering($data[$field], $rule[1]);
111 | }
112 | }
113 | }
114 |
115 | return $filtered;
116 | }
117 |
118 | /**
119 | * value sanitize Filter the value directly
120 | *
121 | * @param mixed $value
122 | * @param array|string $filters
123 | *
124 | * @return mixed
125 | */
126 | public function sanitize(mixed $value, array|string $filters): mixed
127 | {
128 | return $this->valueFiltering($value, $filters);
129 | }
130 |
131 | /**
132 | * get a field value from {@see $data}
133 | *
134 | * @param int|string $field
135 | * @param array|string|null $filters
136 | * @param mixed|null $default
137 | *
138 | * @return mixed
139 | */
140 | public function get(int|string $field, array|string $filters = null, mixed $default = null): mixed
141 | {
142 | if (!isset($this->_data[$field])) {
143 | return $default;
144 | }
145 |
146 | $value = $this->_data[$field];
147 |
148 | if (!$filters) {
149 | return $value;
150 | }
151 |
152 | return $this->valueFiltering($value, $filters);
153 | }
154 |
155 | /**
156 | * @param int|string $field
157 | *
158 | * @return bool
159 | */
160 | public function has(int|string $field): bool
161 | {
162 | return isset($this->_data[$field]);
163 | }
164 |
165 | /**
166 | * @param bool $clearFilters
167 | *
168 | * @return $this
169 | */
170 | public function reset(bool $clearFilters = false): self
171 | {
172 | $this->_data = $this->_rules = [];
173 |
174 | if ($clearFilters) {
175 | $this->clearFilters();
176 | }
177 |
178 | return $this;
179 | }
180 |
181 | /**
182 | * @return array
183 | */
184 | public function getData(): array
185 | {
186 | return $this->_data;
187 | }
188 |
189 | /**
190 | * @return array
191 | */
192 | public function all(): array
193 | {
194 | return $this->_data;
195 | }
196 |
197 | /**
198 | * @param array $rules
199 | *
200 | * @return self
201 | */
202 | public function setRules(array $rules): self
203 | {
204 | $this->_rules = $rules;
205 | return $this;
206 | }
207 |
208 | /**
209 | * @return array
210 | */
211 | public function getRules(): array
212 | {
213 | return $this->_rules;
214 | }
215 | }
216 |
--------------------------------------------------------------------------------
/src/Filter/UserFilters.php:
--------------------------------------------------------------------------------
1 | $filter) {
79 | self::set($name, $filter);
80 | }
81 | }
82 |
83 | /**
84 | * @param string $name
85 | */
86 | public static function remove(string $name): void
87 | {
88 | if (isset(self::$filters[$name])) {
89 | unset(self::$filters[$name]);
90 | }
91 | }
92 |
93 | /**
94 | * clear all filters
95 | */
96 | public static function removeAll(): void
97 | {
98 | self::$filters = [];
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/src/GlobalOption.php:
--------------------------------------------------------------------------------
1 | 'image/bmp',
60 | 'gif' => 'image/gif',
61 | 'ief' => 'image/ief',
62 | 'jpeg' => 'image/jpeg',
63 | 'jpg' => 'image/jpeg',
64 | 'jpe' => 'image/jpeg',
65 | 'png' => 'image/png',
66 | 'svg' => 'image/svg+xml',
67 | 'ico' => 'image/x-icon',
68 | ];
69 |
70 | /**
71 | * @var array
72 | */
73 | public static array $imgMimeConstants = [
74 | IMAGETYPE_GIF,
75 | IMAGETYPE_JPEG,
76 | IMAGETYPE_PNG,
77 | IMAGETYPE_BMP,
78 | IMAGETYPE_WBMP,
79 | IMAGETYPE_ICO
80 | ];
81 |
82 | /** @var string */
83 | public static string $fileValidators = '|file|image|mimeTypes|mimes|';
84 |
85 | /**
86 | * @param string $ext
87 | *
88 | * @return string
89 | */
90 | public static function getImageMime(string $ext): string
91 | {
92 | return self::$imgMimeTypes[$ext] ?? '';
93 | }
94 |
95 | /**
96 | * @param string $mime
97 | *
98 | * @return string|null
99 | */
100 | public static function getImageExtByMime(string $mime): string
101 | {
102 | $key = array_search($mime, self::$imgMimeTypes, true);
103 | return (string)$key;
104 | }
105 |
106 | /**
107 | * @param string $file
108 | *
109 | * @return string eg: 'image/gif'
110 | */
111 | public static function getMimeType(string $file): string
112 | {
113 | if (!file_exists($file)) {
114 | return '';
115 | }
116 |
117 | if (function_exists('mime_content_type')) {
118 | /** @noinspection PhpComposerExtensionStubsInspection */
119 | return mime_content_type($file);
120 | }
121 |
122 | if (function_exists('finfo_file')) {
123 | /** @noinspection PhpComposerExtensionStubsInspection */
124 | return (string)finfo_file(finfo_open(FILEINFO_MIME_TYPE), $file);
125 | }
126 |
127 | return '';
128 | }
129 |
130 | /**
131 | * @param array|int|string $val
132 | *
133 | * @return int
134 | */
135 | public static function length(array|int|string $val): int
136 | {
137 | if (is_int($val)) {
138 | return $val;
139 | }
140 |
141 | if (is_string($val)) {
142 | return self::strlen($val);
143 | }
144 |
145 | return is_array($val) ? count($val) : -1;
146 | }
147 |
148 | /**
149 | * @param string $str
150 | * @param string $encoding
151 | *
152 | * @return int
153 | */
154 | public static function strlen(string $str, string $encoding = 'UTF-8'): int
155 | {
156 | $str = html_entity_decode($str, ENT_COMPAT, 'UTF-8');
157 |
158 | if (function_exists('mb_strlen')) {
159 | return mb_strlen($str, $encoding);
160 | }
161 |
162 | return strlen($str);
163 | }
164 |
165 | /**
166 | * @param string $str
167 | * @param string $find
168 | * @param int $offset
169 | * @param string $encoding
170 | *
171 | * @return bool|int
172 | */
173 | public static function strPos(string $str, string $find, int $offset = 0, string $encoding = 'UTF-8'): bool|int
174 | {
175 | if (function_exists('mb_strpos')) {
176 | return mb_strpos($str, $find, $offset, $encoding);
177 | }
178 |
179 | return strpos($str, $find, $offset);
180 | }
181 |
182 | /**
183 | * @param string $str
184 | * @param string $find
185 | * @param int $offset
186 | * @param string $encoding
187 | *
188 | * @return bool|int
189 | */
190 | public static function strrpos(string $str, string $find, int $offset = 0, string $encoding = 'utf-8'): bool|int
191 | {
192 | if (function_exists('mb_strrpos')) {
193 | return mb_strrpos($str, $find, $offset, $encoding);
194 | }
195 |
196 | return strrpos($str, $find, $offset);
197 | }
198 |
199 | /**
200 | * @param string $field
201 | *
202 | * @return string
203 | */
204 | public static function prettifyFieldName(string $field): string
205 | {
206 | $str = Filters::snakeCase($field, ' ');
207 |
208 | return strpos($str, '_') ? str_replace('_', ' ', $str) : $str;
209 | }
210 |
211 | /**
212 | * @param string $scene Current scene value
213 | * @param array|string $ruleOn
214 | *
215 | * @return bool
216 | */
217 | public static function ruleIsAvailable(string $scene, array|string $ruleOn): bool
218 | {
219 | // - rule is not limit scene
220 | if (!$ruleOn) {
221 | return true;
222 | }
223 |
224 | // - $ruleOn is not empty
225 |
226 | // current scene is empty
227 | if (!$scene) {
228 | return false;
229 | }
230 |
231 | $scenes = is_string($ruleOn) ? Filters::explode($ruleOn) : $ruleOn;
232 |
233 | return in_array($scene, $scenes, true);
234 | }
235 |
236 | /**
237 | * getValueOfArray 支持以 '.' 分割进行子级值获取 eg: 'goods.apple'
238 | *
239 | * @param array $array
240 | * @param array|string|null $key
241 | * @param mixed|null $default
242 | *
243 | * @return mixed
244 | */
245 | public static function getValueOfArray(array $array, array|string|null $key, mixed $default = null): mixed
246 | {
247 | if (null === $key) {
248 | return $array;
249 | }
250 |
251 | if (array_key_exists($key, $array)) {
252 | return $array[$key];
253 | }
254 |
255 | if (!strpos($key, '.')) {
256 | return $default;
257 | }
258 |
259 | $found = false;
260 | foreach (explode('.', $key) as $segment) {
261 | if (is_array($array) && array_key_exists($segment, $array)) {
262 | $found = true;
263 | $array = $array[$segment];
264 | } else {
265 | return $default;
266 | }
267 | }
268 |
269 | return $found ? $array : $default;
270 | }
271 |
272 | /**
273 | * @param callable|mixed $cb
274 | * @param array $args
275 | *
276 | * @return mixed
277 | * @throws InvalidArgumentException
278 | */
279 | public static function call(mixed $cb, ...$args): mixed
280 | {
281 | if (is_string($cb)) {
282 | // className::method
283 | if (strpos($cb, '::') > 0) {
284 | $cb = explode('::', $cb, 2);
285 | // function
286 | } elseif (function_exists($cb)) {
287 | return $cb(...$args);
288 | }
289 | } elseif (is_object($cb) && method_exists($cb, '__invoke')) {
290 | return $cb(...$args);
291 | }
292 |
293 | if (is_array($cb)) {
294 | [$obj, $mhd] = $cb;
295 |
296 | return is_object($obj) ? $obj->$mhd(...$args) : $obj::$mhd(...$args);
297 | }
298 |
299 | throw new InvalidArgumentException('The parameter is not a callable');
300 | }
301 |
302 | /**
303 | * compare of size
304 | * - int Compare size
305 | * - string Compare length
306 | * - array Compare length
307 | *
308 | * @param int|string|array $val
309 | * @param string $operator
310 | * @param int|string|array $expected
311 | *
312 | * @return bool
313 | */
314 | public static function compareSize(int|string|array $val, string $operator, int|string|array $expected): bool
315 | {
316 | // type must be same
317 | if (gettype($val) !== gettype($expected)) {
318 | return false;
319 | }
320 |
321 | // not in: int, string, array
322 | if (($len = self::length($val)) < 0) {
323 | return false;
324 | }
325 |
326 | $wantLen = self::length($expected);
327 |
328 | switch ($operator) {
329 | case '>':
330 | $ok = $len > $wantLen;
331 | break;
332 | case '>=':
333 | $ok = $len >= $wantLen;
334 | break;
335 | case '<':
336 | $ok = $len < $wantLen;
337 | break;
338 | case '<=':
339 | $ok = $len <= $wantLen;
340 | break;
341 | default:
342 | $ok = false;
343 | }
344 |
345 | return $ok;
346 | }
347 |
348 | /**
349 | * @param mixed $val
350 | * @param array $list
351 | *
352 | * @return bool
353 | */
354 | public static function inArray(mixed $val, array $list): bool
355 | {
356 | if (!is_scalar($val)) {
357 | return false;
358 | }
359 |
360 | $valType = gettype($val);
361 | foreach ($list as $item) {
362 | if (!is_scalar($item)) {
363 | continue;
364 | }
365 |
366 | // compare value
367 | switch ($valType) {
368 | case 'integer':
369 | $exist = $val === (int)$item;
370 | break;
371 | case 'float':
372 | case 'double':
373 | case 'string':
374 | $exist = (string)$val === (string)$item;
375 | break;
376 | default:
377 | return false;
378 | }
379 |
380 | if ($exist) {
381 | return true;
382 | }
383 | }
384 |
385 | return false;
386 | }
387 | }
388 |
--------------------------------------------------------------------------------
/src/Locale/LocaleZhCN.php:
--------------------------------------------------------------------------------
1 | [
22 | '{attr} 必须是整数!',
23 | '{attr} 必须是整数并且最小值是 {min}',
24 | '{attr} 必须是整数并且值的范围必须在 {min} ~ {max}',
25 | ],
26 | // 'num'
27 | 'number' => [
28 | '{attr} 必须是整数并且大于0!',
29 | '{attr} 必须是整数并且最小值是 {min}',
30 | '{attr} 必须是整数并且值的范围必须在 {min} ~ {max}',
31 | ],
32 | // 'bool', 'boolean',
33 | 'boolean' => '{attr} 必须是布尔类型!',
34 | 'float' => '{attr} 必须是浮点数!',
35 | 'url' => '{attr} 不是url地址!',
36 | 'email' => '{attr} 不是电子邮件地址!',
37 | 'date' => '{attr} 不是日期格式!',
38 | 'dateFormat' => '{attr} is not in a {value0} date format!',
39 | 'ip' => '{attr} 不是IP地址!',
40 | 'ipv4' => '{attr} 不是 IPv4 地址!',
41 | 'ipv6' => '{attr} 不是IPv6地址!',
42 | 'required' => '字段 {attr} 是必须的!',
43 | 'requiredIf' => '字段 {attr} 是必须的!',
44 | 'length' => [
45 | '{attr} 长度验证未通过!',
46 | '{attr} 必须是一个字符串/数组,最小长度是 {min}',
47 | '{attr} 值不合法,长度范围只允许是 {min} ~ {max}',
48 | ],
49 | // 'range', 'between'
50 | 'size' => [
51 | '{attr} size validation is not through!',
52 | '{attr} 值不合法,最小是 {min}',
53 | // '{attr} must be an integer/string/array and value/length range {min} ~ {max}',
54 | '{attr} 值不合法,大小范围只允许是 {min} ~ {max}',
55 | ],
56 |
57 | // 'lengthEq', 'sizeEq'
58 | 'fixedSize' => '{attr} length must is {value0}',
59 |
60 | 'eq' => '{attr} must be equals to {value0}',
61 | // 'different'
62 | 'ne' => '{attr} can not be equals to {value0}',
63 | 'min' => '{attr} minimum boundary is {value0}',
64 | 'max' => '{attr} maximum boundary is {value0}',
65 | 'lt' => '{attr} value must be less than {value0}',
66 | 'lte' => '{attr} value must be less than or equals to {value0}',
67 | 'gt' => '{attr} value must be greater than {value0}',
68 | 'gte' => '{attr} value must be greater than or equals to {value0}',
69 |
70 | // field compare
71 | 'eqField' => '{attr} 值必须等于 {value0} 的值',
72 | 'neqField' => '{attr} 值不能等于 {value0} 的值',
73 | 'ltField' => '{attr} 值必须小于 {value0} 的值',
74 | 'lteField' => '{attr} 值必须小于或等于 {value0} 的值',
75 | 'gtField' => '{attr} 值必须大于 {value0} 的值',
76 | 'gteField' => '{attr} 值必须大于或等于 {value0} 的值',
77 |
78 | // 'in', 'enum',
79 | 'enum' => '{attr} must in ({value0})',
80 | 'notIn' => '{attr} cannot in ({value0})',
81 |
82 | 'string' => [
83 | '{attr} 必须是字符串',
84 | '{attr} 必须是字符串并且最小长度为 {min}',
85 | '{attr} 必须是字符串并且长度范围必须是 {min} ~ {max}',
86 | ],
87 |
88 | // 'regex', 'regexp',
89 | 'regexp' => '{attr} does not match the {value0} conditions',
90 |
91 | 'mustBe' => '{attr} must be equals to {value0}',
92 | 'notBe' => '{attr} can not be equals to {value0}',
93 |
94 | 'compare' => '{attr} must be equals to {value0}',
95 | 'same' => '{attr} must be equals to {value0}',
96 |
97 | 'isArray' => '{attr} 必须是数组',
98 | 'isMap' => '{attr} 必须是数组并且是键-值对格式',
99 | 'isList' => '{attr} 必须是自然数组',
100 | 'intList' => '{attr} 必须是一个数组并且值都是整数',
101 | 'numList' => '{attr} 必须是一个数组并且值都是大于0的数字',
102 | 'strList' => '{attr} 必须是一个数组并且值都是字符串',
103 | 'arrList' => '{attr} 必须是一个数组,并且值也都是数组',
104 |
105 | 'each' => '{attr} each value must be through the "{value0}" verify',
106 | 'hasKey' => '{attr} 必须包含键字段 {value0}',
107 | 'distinct' => '{attr} 中不应该有重复的值',
108 |
109 | 'json' => '{attr} 必须是 json 字符串',
110 |
111 | 'file' => '{attr} 必须是上传的文件',
112 | 'image' => '{attr} 必须是上传的图片文件',
113 |
114 | 'callback' => '{attr} don\'t pass the test and verify!',
115 | '__default' => '{attr} validation is not through!',
116 | ];
117 |
118 | /**
119 | * register to global message
120 | */
121 | public static function register(): void
122 | {
123 | GlobalMessage::setMessages(self::$messages);
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/src/RV.php:
--------------------------------------------------------------------------------
1 | = $min
204 | * @param int|null $max The value length must be <= $min
205 | * @param array|null $default
206 | *
207 | * @return array
208 | */
209 | public static function getStrings(string $field, ?int $min = null, ?int $max = null, ?array $default = null): array
210 | {
211 | if (!isset(self::$data[$field])) {
212 | if ($default === null) {
213 | throw static::newEx($field, 'is required and must be string array');
214 | }
215 | return $default;
216 | }
217 |
218 | if (!$val = self::$data[$field]) {
219 | if ($default === null) {
220 | throw static::newEx($field, 'is required and must be string array');
221 | }
222 | return $default;
223 | }
224 |
225 | $arr = is_scalar($val) ? [trim((string)$val)] : (array)$val;
226 |
227 | if (!Validators::strList($arr)) {
228 | throw static::newEx($field, 'must be string array');
229 | }
230 |
231 | // check min and max value
232 | if (!Validators::integer(count($arr), $min, $max)) {
233 | throw static::newEx($field, self::fmtMinMaxToMsg('must be array value and length', $min, $max));
234 | }
235 |
236 | return $arr;
237 | }
238 |
239 | /**
240 | * @param string $field
241 | * @param int|null $min
242 | * @param int|null $max
243 | * @param array|null $default
244 | *
245 | * @return array
246 | */
247 | public static function getArray(string $field, ?int $min = null, ?int $max = null, ?array $default = null): array
248 | {
249 | if (!isset(self::$data[$field])) {
250 | if ($default === null) {
251 | throw static::newEx($field, 'is required and must be array');
252 | }
253 |
254 | return $default;
255 | }
256 |
257 | if (!$val = self::$data[$field]) {
258 | if ($default === null) {
259 | throw static::newEx($field, 'is required and must be array');
260 | }
261 | return $default;
262 | }
263 |
264 | $arr = is_scalar($val) ? [$val] : (array)$val;
265 |
266 | // check min and max value
267 | if (!Validators::integer(count($arr), $min, $max)) {
268 | throw static::newEx($field, self::fmtMinMaxToMsg('must be array value and length', $min, $max));
269 | }
270 |
271 | return $arr;
272 | }
273 |
274 | /**
275 | * @param string $field
276 | * @param int|null $min
277 | * @param int|null $max
278 | * @param array|null $default
279 | * @param string $sep
280 | *
281 | * @return array
282 | */
283 | public static function getArrayBySplit(string $field, ?int $min = null, ?int $max = null, ?array $default = null, string $sep = ','): array
284 | {
285 | if (!isset(self::$data[$field])) {
286 | if ($default === null) {
287 | throw self::newEx($field, 'is required and must be string');
288 | }
289 | return $default;
290 | }
291 |
292 | $val = self::$data[$field];
293 | if (!$val || !is_string($val)) {
294 | if ($default === null) {
295 | throw self::newEx($field, 'is required and must be string');
296 | }
297 | return $default;
298 | }
299 |
300 | $arr = Str::explode($val, $sep);
301 |
302 | // check min and max value
303 | if (!Validators::integer(count($arr), $min, $max)) {
304 | throw static::newEx($field, self::fmtMinMaxToMsg('array value length', $min, $max));
305 | }
306 |
307 | return $arr;
308 | }
309 |
310 | /**
311 | * @param string $field
312 | * @param int|null $min
313 | * @param int|null $max
314 | * @param array|null $default
315 | *
316 | * @return array
317 | */
318 | public static function getArrayByJSON(string $field, ?int $min = null, ?int $max = null, ?array $default = null): array
319 | {
320 | if (!isset(self::$data[$field])) {
321 | if ($default === null) {
322 | throw new ValidateException($field, 'is required and must be JSON string');
323 | }
324 |
325 | return $default;
326 | }
327 |
328 | $val = self::$data[$field];
329 | if (!$val || !is_string($val)) {
330 | if ($default === null) {
331 | throw self::newEx($field, 'is required and must be JSON string');
332 | }
333 | return $default;
334 | }
335 |
336 | // must start with: { OR [
337 | if ('[' !== $val[0] && '{' !== $val[0]) {
338 | throw self::newEx($field, 'must be valid JSON string');
339 | }
340 |
341 | $arr = json_decode($val, true, 512, JSON_THROW_ON_ERROR);
342 | if (json_last_error() !== JSON_ERROR_NONE) {
343 | throw self::newEx($field, 'must be valid JSON string');
344 | }
345 |
346 | // check min and max value
347 | if (!Validators::integer(count($arr), $min, $max)) {
348 | throw static::newEx($field, self::fmtMinMaxToMsg('array value length', $min, $max));
349 | }
350 |
351 | return $arr;
352 | }
353 |
354 | /**
355 | * @param string $prefix
356 | * @param int|null $min
357 | * @param int|null $max
358 | * @param string $sepMsg
359 | *
360 | * @return string
361 | */
362 | public static function fmtMinMaxToMsg(string $prefix, ?int $min = null, ?int $max = null, string $sepMsg = 'and'): string
363 | {
364 | if ($min !== null && $max !== null) {
365 | return "$prefix $sepMsg must be >= $min and <= $max";
366 | }
367 |
368 | // check min and max value
369 | if ($min !== null) {
370 | return "$prefix $sepMsg must be greater or equal to $min";
371 | }
372 |
373 | if ($max !== null) {
374 | return "$prefix $sepMsg must be less than or equal to $max";
375 | }
376 |
377 | return $prefix;
378 | }
379 |
380 | /**
381 | * @param string $field
382 | * @param string $errMsg
383 | *
384 | * @return ValidateException
385 | */
386 | public static function newEx(string $field, string $errMsg): ValidateException
387 | {
388 | /** @psalm-var class-string $class */
389 | $class = self::$throwClass ?: ValidateException::class;
390 | return new $class($field, $errMsg, self::$throwCode);
391 | }
392 |
393 | /**
394 | * @return int
395 | */
396 | public static function getThrowCode(): int
397 | {
398 | return self::$throwCode;
399 | }
400 |
401 | /**
402 | * @param int $throwCode
403 | */
404 | public static function setThrowCode(int $throwCode): void
405 | {
406 | self::$throwCode = $throwCode;
407 | }
408 |
409 | /**
410 | * @return string
411 | */
412 | public static function getThrowClass(): string
413 | {
414 | return self::$throwClass;
415 | }
416 |
417 | /**
418 | * @param string $throwClass
419 | */
420 | public static function setThrowClass(string $throwClass): void
421 | {
422 | self::$throwClass = $throwClass;
423 | }
424 | }
425 |
--------------------------------------------------------------------------------
/src/Simple/ValidValue.php:
--------------------------------------------------------------------------------
1 | ?string,
30 | * 'message' => string,
31 | * ];
32 | * ```
33 | *
34 | * @param string $value
35 | * @param int|null $min
36 | * @param int|null $max
37 | * @param array $opt
38 | *
39 | * @return string
40 | */
41 | public static function getString(string $value, ?int $min, ?int $max, array $opt = []): string
42 | {
43 | return '';
44 | }
45 |
46 | /**
47 | * @return int
48 | */
49 | public static function getThrowCode(): int
50 | {
51 | return self::$throwCode;
52 | }
53 |
54 | /**
55 | * @param int $throwCode
56 | */
57 | public static function setThrowCode(int $throwCode): void
58 | {
59 | self::$throwCode = $throwCode;
60 | }
61 |
62 | /**
63 | * @return string
64 | */
65 | public static function getThrowClass(): string
66 | {
67 | return self::$throwClass;
68 | }
69 |
70 | /**
71 | * @param string $throwClass
72 | */
73 | public static function setThrowClass(string $throwClass): void
74 | {
75 | self::$throwClass = $throwClass;
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/src/Traits/ErrorMessageTrait.php:
--------------------------------------------------------------------------------
1 | 'field', 'msg' => 'error Message1' ],
45 | * ['name' => 'field2', 'msg' => 'error Message2' ],
46 | * ]
47 | *
48 | * @var array[]
49 | */
50 | private array $_errors = [];
51 |
52 | /**
53 | * Whether there is error stop validation 是否出现验证失败就立即停止验证
54 | * True -- 出现一个验证失败即停止验证,并退出
55 | * False -- 全部验证并将错误信息保存到 {@see $_errors}
56 | *
57 | * @var boolean
58 | */
59 | private bool $_stopOnError = true;
60 |
61 | /**
62 | * Prettify field name on get error message
63 | *
64 | * @var bool
65 | */
66 | private bool $_prettifyName = true;
67 |
68 | protected function prepareValidation(): void
69 | {
70 | // error message
71 | $this->_messages = array_merge($this->messages(), $this->_messages);
72 | // field translate
73 | $this->_translates = array_merge($this->translates(), $this->_translates);
74 | }
75 |
76 | /*******************************************************************************
77 | * Errors Information
78 | ******************************************************************************/
79 |
80 | /**
81 | * @return bool
82 | */
83 | protected function shouldStop(): bool
84 | {
85 | return $this->isFail() && $this->_stopOnError;
86 | }
87 |
88 | /**
89 | * Is there an error?
90 | *
91 | * @return boolean
92 | */
93 | public function hasError(): bool
94 | {
95 | return $this->isFail();
96 | }
97 |
98 | /**
99 | * @return bool
100 | */
101 | public function isFail(): bool
102 | {
103 | return count($this->_errors) > 0;
104 | }
105 |
106 | /**
107 | * @return bool
108 | */
109 | public function failed(): bool
110 | {
111 | return $this->isFail();
112 | }
113 |
114 | /**
115 | * @return bool
116 | * @deprecated will delete, please use isOk() or isPassed() instead
117 | */
118 | public function ok(): bool
119 | {
120 | return !$this->isFail();
121 | }
122 |
123 | /**
124 | * @return bool
125 | */
126 | public function isOk(): bool
127 | {
128 | return !$this->isFail();
129 | }
130 |
131 | /**
132 | * @return bool
133 | * @deprecated will delete, please use isOk() or isPassed() instead
134 | */
135 | public function passed(): bool
136 | {
137 | return !$this->isFail();
138 | }
139 |
140 | /**
141 | * @return bool
142 | */
143 | public function isPassed(): bool
144 | {
145 | return !$this->isFail();
146 | }
147 |
148 | /**
149 | * check field whether in the errors
150 | *
151 | * @param string $field
152 | *
153 | * @return bool
154 | */
155 | public function inError(string $field): bool
156 | {
157 | foreach ($this->_errors as $item) {
158 | if ($field === $item['name']) {
159 | return true;
160 | }
161 | }
162 | return false;
163 | }
164 |
165 | /**
166 | * @param string $field
167 | * @param string $msg
168 | */
169 | public function addError(string $field, string $msg): void
170 | {
171 | $this->_errors[] = [
172 | 'name' => $field,
173 | 'msg' => $msg,
174 | ];
175 | }
176 |
177 | /**
178 | * @param string $field Only get errors of the field.
179 | *
180 | * @return array
181 | */
182 | public function getErrors(string $field = ''): array
183 | {
184 | if ($field) {
185 | $errors = [];
186 | foreach ($this->_errors as $item) {
187 | if ($field === $item['name']) {
188 | $errors[] = $item['msg'];
189 | }
190 | }
191 |
192 | return $errors;
193 | }
194 |
195 | return $this->_errors;
196 | }
197 |
198 | /**
199 | * clear errors
200 | */
201 | public function clearErrors(): void
202 | {
203 | $this->_errors = [];
204 | }
205 |
206 | /**
207 | * Get the first error message
208 | *
209 | * @param bool $onlyMsg
210 | *
211 | * @return array|string
212 | */
213 | public function firstError(bool $onlyMsg = true): array|string
214 | {
215 | if (!$errors = $this->_errors) {
216 | return $onlyMsg ? '' : [];
217 | }
218 |
219 | $first = array_shift($errors);
220 | return $onlyMsg ? $first['msg'] : $first;
221 | }
222 |
223 | /**
224 | * Get the last error message
225 | *
226 | * @param bool $onlyMsg
227 | *
228 | * @return array|string
229 | */
230 | public function lastError(bool $onlyMsg = true): array|string
231 | {
232 | if (!$errors = $this->_errors) {
233 | return $onlyMsg ? '' : [];
234 | }
235 |
236 | $last = array_pop($errors);
237 | return $onlyMsg ? $last['msg'] : $last;
238 | }
239 |
240 | /**
241 | * @param mixed|null $_stopOnError
242 | *
243 | * @return static
244 | */
245 | public function setStopOnError(mixed $_stopOnError = null): static
246 | {
247 | if (null !== $_stopOnError) {
248 | $this->_stopOnError = (bool)$_stopOnError;
249 | }
250 | return $this;
251 | }
252 |
253 | /**
254 | * @return bool
255 | */
256 | public function isStopOnError(): bool
257 | {
258 | return $this->_stopOnError;
259 | }
260 |
261 | /*******************************************************************************
262 | * Error Messages
263 | ******************************************************************************/
264 |
265 | /**
266 | * @param string $key
267 | * @param array|string $message
268 | */
269 | public function setMessage(string $key, array|string $message): void
270 | {
271 | if ($key && $message) {
272 | $this->_messages[$key] = $message;
273 | }
274 | }
275 |
276 | /**
277 | * @return array
278 | */
279 | public function getMessages(): array
280 | {
281 | return $this->_messages;
282 | }
283 |
284 | /**
285 | * @param array $messages
286 | *
287 | * @return static
288 | */
289 | public function setMessages(array $messages): static
290 | {
291 | foreach ($messages as $key => $value) {
292 | $this->setMessage($key, $value);
293 | }
294 | return $this;
295 | }
296 |
297 | /**
298 | * 各个验证器的提示消息
299 | *
300 | * @param callable|string $validator 验证器
301 | * @param string $field
302 | * @param array $args
303 | * @param array|string|null $message 自定义提示消息
304 | *
305 | * @return string
306 | */
307 | public function getMessage(callable|string $validator, string $field, array $args = [], array|string $message = null): string
308 | {
309 | $rawName = is_string($validator) ? $validator : 'callback';
310 | $params = [
311 | '{attr}' => $this->getTranslate($field)
312 | ];
313 |
314 | // get message from built in dict.
315 | if (!$message) {
316 | $message = $this->findMessage($field, $rawName) ?: GlobalMessage::getDefault();
317 | // is array. It's defined multi error messages
318 | } elseif (is_array($message)) {
319 | $message = $message[$field] ?? $message[$rawName] ?? $this->findMessage($field, $rawName);
320 |
321 | if (!$message) { // use default
322 | return strtr(GlobalMessage::getDefault(), $params);
323 | }
324 | }
325 |
326 | /** @see GlobalMessage::$messages['size'] */
327 | if (is_array($message)) {
328 | $msgKey = count($args);
329 | $message = $message[$msgKey] ?? $message[0];
330 | }
331 |
332 | if (!str_contains($message, '{')) {
333 | return $message;
334 | }
335 |
336 | foreach ($args as $key => $value) {
337 | $key = is_int($key) ? "value$key" : $key;
338 | // build params
339 | $params['{' . $key . '}'] = is_array($value) ? implode(',', $value) : $value;
340 | }
341 |
342 | return strtr($message, $params);
343 | }
344 |
345 | /**
346 | * @param string $field
347 | * @param string $rawName
348 | *
349 | * @return string|array
350 | */
351 | protected function findMessage(string $field, string $rawName): array|string
352 | {
353 | // allow define a message for a validator.
354 | // eg: 'username.required' => 'some message ...'
355 | $fullKey = $field . '.' . $rawName;
356 | $realName = Validators::realName($rawName);
357 |
358 | // get from default
359 | if (!$this->_messages) {
360 | return GlobalMessage::get($realName);
361 | }
362 |
363 | if (isset($this->_messages[$fullKey])) {
364 | $message = $this->_messages[$fullKey];
365 | // eg 'required' => 'some message ...'
366 | } elseif (isset($this->_messages[$rawName])) {
367 | $message = $this->_messages[$rawName];
368 | } elseif (isset($this->_messages[$realName])) {
369 | $message = $this->_messages[$realName];
370 | } else { // get from default
371 | $message = GlobalMessage::get($realName);
372 | }
373 |
374 | return $message;
375 | }
376 |
377 | /**
378 | * set the attrs translation data
379 | *
380 | * @param array $fieldTrans
381 | *
382 | * @return static
383 | */
384 | public function setTranslates(array $fieldTrans): static
385 | {
386 | return $this->addTranslates($fieldTrans);
387 | }
388 |
389 | /**
390 | * add the attrs translation data
391 | *
392 | * @param array $fieldTrans
393 | *
394 | * @return static
395 | */
396 | public function addTranslates(array $fieldTrans): static
397 | {
398 | foreach ($fieldTrans as $field => $tran) {
399 | $this->_translates[$field] = $tran;
400 | }
401 | return $this;
402 | }
403 |
404 | /**
405 | * @return array
406 | */
407 | public function getTranslates(): array
408 | {
409 | return $this->_translates;
410 | }
411 |
412 | /**
413 | * get field translate string.
414 | *
415 | * @param string $field
416 | *
417 | * @return string
418 | */
419 | public function getTranslate(string $field): string
420 | {
421 | $trans = $this->getTranslates();
422 |
423 | if (isset($trans[$field])) {
424 | return $trans[$field];
425 | }
426 |
427 | if ($this->_prettifyName) {
428 | return Helper::prettifyFieldName($field);
429 | }
430 |
431 | return $field;
432 | }
433 |
434 | /**
435 | * @return array
436 | */
437 | public function clearTranslates(): array
438 | {
439 | return $this->_translates = [];
440 | }
441 |
442 | /**
443 | * @return bool
444 | */
445 | public function isPrettifyName(): bool
446 | {
447 | return $this->_prettifyName;
448 | }
449 |
450 | /**
451 | * @param bool $prettifyName
452 | */
453 | public function setPrettifyName(bool $prettifyName = true): void
454 | {
455 | $this->_prettifyName = $prettifyName;
456 | }
457 | }
458 |
--------------------------------------------------------------------------------
/src/Traits/MultipleRulesTrait.php:
--------------------------------------------------------------------------------
1 | scene;
55 |
56 | // 循环规则, 搜集当前场景可用的规则
57 | foreach ($this->getRules() as $rule) {
58 | // check field
59 | if (!isset($rule[0]) || !$rule[0]) {
60 | throw new InvalidArgumentException('Please setting the field(string) to wait validate! position: rule[0]');
61 | }
62 |
63 | // check validators
64 | if (!isset($rule[1]) || !$rule[1]) {
65 | throw new InvalidArgumentException('Please setting the validator(s) for validate field! position: rule[1]');
66 | }
67 |
68 | // rule only allow use to special scene.
69 | if (isset($rule['on'])) {
70 | if (!Helper::ruleIsAvailable($scene, $rule['on'])) {
71 | continue;
72 | }
73 |
74 | unset($rule['on']);
75 | }
76 |
77 | $this->_usedRules[] = $rule;
78 | // field
79 | $field = array_shift($rule);
80 |
81 | // if is a Closure
82 | if (is_object($rule[0])) {
83 | yield $field => $rule;
84 | } else {
85 | // 'required|string:5,10;' OR 'required|in:5,10'
86 | $rules = is_array($rule[0]) ? $rule[0] : array_map('trim', explode('|', $rule[0]));
87 |
88 | foreach ($rules as $aRule) {
89 | yield $field => $this->parseRule($aRule, $rule);
90 | }
91 | }
92 | }
93 | }
94 |
95 | /**
96 | * @param string $rule
97 | * @param array $row
98 | *
99 | * @return array
100 | */
101 | protected function parseRule(string $rule, array $row): array
102 | {
103 | $rule = trim($rule, ': ');
104 | if (!str_contains($rule, ':')) {
105 | $row[0] = $rule;
106 | return $row;
107 | }
108 |
109 | [$name, $args] = Filters::explode($rule, ':', 2);
110 | $args = trim($args, ', ');
111 | $row[0] = $name;
112 |
113 | switch ($name) {
114 | case 'in':
115 | case 'enum':
116 | case 'ontIn':
117 | $row[] = Filters::explode($args);
118 | break;
119 |
120 | case 'size':
121 | case 'range':
122 | case 'string':
123 | case 'between':
124 | if (strpos($args, ',')) {
125 | [$row['min'], $row['max']] = Filters::explode($args, ',', 2);
126 | } else {
127 | $row['min'] = $args;
128 | }
129 | break;
130 | default:
131 | $row = array_merge($row, Filters::explode($args));
132 | break;
133 | }
134 |
135 | return $row;
136 | }
137 | }
138 |
--------------------------------------------------------------------------------
/src/Traits/NameAliasTrait.php:
--------------------------------------------------------------------------------
1 | $alias) {
57 | static::$aliases[$name] = $alias;
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/Traits/ScopedValidatorsTrait.php:
--------------------------------------------------------------------------------
1 | addValidator('my-validator', function($val [, $arg1, $arg2 ... ]){
57 | * return $val === 23;
58 | * });
59 | * $v->validate();
60 | * ```
61 | *
62 | * @param string $name
63 | * @param callable $callback
64 | * @param string $message
65 | *
66 | * @return AbstractValidation|ScopedValidatorsTrait|DataModel
67 | */
68 | public function addValidator(string $name, callable $callback, string $message = ''): self
69 | {
70 | return $this->setValidator($name, $callback, $message);
71 | }
72 |
73 | /**
74 | * add a custom validator
75 | *
76 | * @param string $name
77 | * @param callable $callback
78 | * @param string $message
79 | *
80 | * @return static
81 | */
82 | public function setValidator(string $name, callable $callback, string $message = ''): self
83 | {
84 | if ($name = trim($name)) {
85 | $this->_validators[$name] = $callback;
86 |
87 | if ($message) {
88 | $this->setMessage($name, $message);
89 | }
90 | }
91 |
92 | return $this;
93 | }
94 |
95 | /**
96 | * @param string $name
97 | *
98 | * @return null|callable
99 | */
100 | public function getValidator(string $name): ?callable
101 | {
102 | return $this->_validators[$name] ?? null;
103 | }
104 |
105 | /**
106 | * @param string $name
107 | *
108 | * @return bool
109 | */
110 | public function hasValidator(string $name): bool
111 | {
112 | return isset($this->_validators[$name]);
113 | }
114 |
115 | /**
116 | * @param array $validators
117 | *
118 | * @return $this
119 | */
120 | public function addValidators(array $validators): self
121 | {
122 | foreach ($validators as $name => $validator) {
123 | $this->addValidator($name, $validator);
124 | }
125 | return $this;
126 | }
127 |
128 | /**
129 | * @return array
130 | */
131 | public function getValidators(): array
132 | {
133 | return $this->_validators;
134 | }
135 |
136 | /**
137 | * @param string $name
138 | */
139 | public function delValidator(string $name): void
140 | {
141 | if (isset($this->_validators[$name])) {
142 | unset($this->_validators[$name]);
143 | }
144 | }
145 |
146 | /**
147 | * clear validators
148 | */
149 | public function clearValidators(): void
150 | {
151 | $this->_validators = [];
152 | }
153 |
154 | /*******************************************************************************
155 | * fields(required*, file) validators
156 | ******************************************************************************/
157 |
158 | /**
159 | * 验证字段必须存在,且输入数据不为空。
160 | * The verification field must exist and the input data is not empty.
161 | *
162 | * @param string $field
163 | * @param mixed|null $value
164 | *
165 | * @return bool
166 | * - True field exists and value is not empty.
167 | * - False field not exists or value is empty.
168 | * @see Validators::isEmpty() 如何鉴定为空
169 | */
170 | public function required(string $field, mixed $value = null): bool
171 | {
172 | if (null !== $value) {
173 | $val = $value;
174 | } elseif (null === ($val = $this->getByPath($field))) {
175 | // check uploaded files
176 | if (!isset($this->uploadedFiles[$field])) {
177 | return false;
178 | }
179 |
180 | $val = $this->uploadedFiles[$field];
181 | }
182 |
183 | return !Validators::isEmpty($val);
184 | }
185 |
186 | /**
187 | * 如果指定的另一个字段( anotherField )值等于任何一个 value 时,此字段为 必填 (refer laravel)
188 | *
189 | * returns:
190 | * - TRUE check successful
191 | * - FALSE check failed
192 | * - NULL skip check the field
193 | *
194 | * @param string $field
195 | * @param mixed $fieldVal
196 | * @param string $anotherField
197 | * @param array|string|int $values
198 | *
199 | * @return bool|null
200 | */
201 | public function requiredIf(string $field, mixed $fieldVal, string $anotherField, array|string|int $values): ?bool
202 | {
203 | if (isset($this->data[$anotherField])) {
204 | $anotherVal = $this->data[$anotherField];
205 |
206 | // if (in_array($anotherVal, (array)$values, true)) {
207 | if (Helper::inArray($anotherVal, (array)$values)) {
208 | return $this->required($field, $fieldVal);
209 | }
210 | }
211 |
212 | return null;
213 | }
214 |
215 | /**
216 | * 如果指定的另一个字段( anotherField )值等于任何一个 value 时,此字段为 不必填(refer laravel)
217 | *
218 | * @param string $field
219 | * @param mixed $fieldVal
220 | * @param string $anotherField
221 | * @param array|string $values
222 | *
223 | * @return bool|null
224 | * @see requiredIf()
225 | */
226 | public function requiredUnless(string $field, mixed $fieldVal, string $anotherField, array|string $values): ?bool
227 | {
228 | if (isset($this->data[$anotherField])) {
229 | $anotherVal = $this->data[$anotherField];
230 |
231 | // if (in_array($anotherVal, (array)$values, true)) {
232 | if (Helper::inArray($anotherVal, (array)$values)) {
233 | return null;
234 | }
235 | }
236 |
237 | return $this->required($field, $fieldVal);
238 | }
239 |
240 | /**
241 | * 如果指定的其他字段中的 任意一个 有值且不为空,则此字段为 必填(refer laravel)
242 | *
243 | * @param string $field
244 | * @param mixed $fieldVal
245 | * @param array|string $fields
246 | *
247 | * @return bool|null
248 | * @see requiredIf()
249 | */
250 | public function requiredWith(string $field, mixed $fieldVal, array|string $fields): ?bool
251 | {
252 | foreach ((array)$fields as $name) {
253 | if ($this->required($name)) {
254 | return $this->required($field, $fieldVal);
255 | }
256 | }
257 |
258 | return null;
259 | }
260 |
261 | /**
262 | * 如果指定的 所有字段 都有值且不为空,则此字段为 必填(refer laravel)
263 | *
264 | * @param string $field
265 | * @param mixed $fieldVal
266 | * @param array|string $fields
267 | *
268 | * @return bool|null
269 | * @see requiredIf()
270 | */
271 | public function requiredWithAll(string $field, mixed $fieldVal, array|string $fields): ?bool
272 | {
273 | $allHasValue = true;
274 |
275 | foreach ((array)$fields as $name) {
276 | if (!$this->required($name)) {
277 | $allHasValue = false;
278 | break;
279 | }
280 | }
281 |
282 | return $allHasValue ? $this->required($field, $fieldVal) : null;
283 | }
284 |
285 | /**
286 | * 如果缺少 任意一个 指定的字段值,则此字段为 必填(refer laravel)
287 | *
288 | * @param string $field
289 | * @param mixed $fieldVal
290 | * @param array|string $fields
291 | *
292 | * @return bool|null
293 | * @see requiredIf()
294 | */
295 | public function requiredWithout(string $field, mixed $fieldVal, array|string $fields): ?bool
296 | {
297 | $allHasValue = true;
298 |
299 | foreach ((array)$fields as $name) {
300 | if (!$this->required($name)) {
301 | $allHasValue = false;
302 | break;
303 | }
304 | }
305 |
306 | return $allHasValue ? null : $this->required($field, $fieldVal);
307 | }
308 |
309 | /**
310 | * 如果所有指定的字段 都没有 值,则此字段为 必填(refer laravel)
311 | *
312 | * @param string $field
313 | * @param mixed $fieldVal
314 | * @param array|string $fields
315 | *
316 | * @return bool|null
317 | * @see requiredIf()
318 | */
319 | public function requiredWithoutAll(string $field, mixed $fieldVal, array|string $fields): ?bool
320 | {
321 | $allNoValue = true;
322 |
323 | foreach ((array)$fields as $name) {
324 | if ($this->required($name)) {
325 | $allNoValue = false;
326 | break;
327 | }
328 | }
329 |
330 | return $allNoValue ? $this->required($field, $fieldVal) : null;
331 | }
332 |
333 | /*******************************************************************************
334 | * Files validators(require context data)
335 | ******************************************************************************/
336 |
337 | /**
338 | * 验证的字段必须是成功上传的文件
339 | *
340 | * @param string $field
341 | * @param array|string|null $suffixes e.g ['jpg', 'jpeg', 'png', 'gif', 'bmp']
342 | *
343 | * @return bool
344 | */
345 | public function fileValidator(string $field, array|string $suffixes = null): bool
346 | {
347 | if (!$file = $this->uploadedFiles[$field] ?? null) {
348 | return false;
349 | }
350 |
351 | if (!isset($file['error']) || ($file['error'] !== UPLOAD_ERR_OK)) {
352 | return false;
353 | }
354 |
355 | if (!$suffixes) {
356 | return true;
357 | }
358 |
359 | if (!$suffix = trim((string)strrchr($file['name'], '.'), '.')) {
360 | return false;
361 | }
362 |
363 | $suffixes = is_string($suffixes) ? Filters::explode($suffixes) : $suffixes;
364 |
365 | return in_array(strtolower($suffix), $suffixes, true);
366 | }
367 |
368 | /**
369 | * 验证的字段必须是成功上传的图片文件
370 | *
371 | * @param string $field
372 | * @param array|string|null $suffixes e.g ['jpg', 'jpeg', 'png', 'gif', 'bmp']
373 | *
374 | * @return bool
375 | */
376 | public function imageValidator(string $field, array|string $suffixes = null): bool
377 | {
378 | if (!$file = $this->uploadedFiles[$field] ?? null) {
379 | return false;
380 | }
381 |
382 | if (!isset($file['error']) || ($file['error'] !== UPLOAD_ERR_OK)) {
383 | return false;
384 | }
385 |
386 | if (!$tmpFile = $file['tmp_name'] ?? null) {
387 | return false;
388 | }
389 |
390 | // getimagesize() 判定某个文件是否为图片的有效手段, 常用在文件上传验证
391 | // Note: 本函数不需要 GD 图像库
392 | // list($width, $height, $type, $attr) = getimagesize("img/flag.jpg");
393 | $imgInfo = @getimagesize($tmpFile);
394 |
395 | if ($imgInfo && $imgInfo[2]) {
396 | $mime = strtolower($imgInfo['mime']); // 支持不标准扩展名
397 |
398 | // 是否是图片
399 | if (!in_array($mime, Helper::$imgMimeTypes, true)) {
400 | return false;
401 | }
402 |
403 | if (!$suffixes) {
404 | return true;
405 | }
406 |
407 | $suffix = Helper::getImageExtByMime($mime);
408 | $suffixes = is_string($suffixes) ? Filters::explode($suffixes) : $suffixes;
409 |
410 | return in_array($suffix, $suffixes, true);
411 | }
412 |
413 | return false;
414 | }
415 |
416 | /**
417 | * 验证的文件必须与给定 MIME 类型之一匹配
418 | * ['video', 'mimeTypes', 'video/avi,video/mpeg,video/quicktime']
419 | *
420 | * @param string $field
421 | * @param array|string $types
422 | *
423 | * @return bool
424 | */
425 | public function mimeTypesValidator(string $field, array|string $types): bool
426 | {
427 | if (!$file = $this->uploadedFiles[$field] ?? null) {
428 | return false;
429 | }
430 |
431 | if (!isset($file['error']) || ($file['error'] !== UPLOAD_ERR_OK)) {
432 | return false;
433 | }
434 |
435 | if (!$tmpFile = $file['tmp_name'] ?? null) {
436 | return false;
437 | }
438 |
439 | $mime = Helper::getMimeType($tmpFile);
440 | $types = is_string($types) ? Filters::explode($types) : (array)$types;
441 |
442 | return in_array($mime, $types, true);
443 | }
444 |
445 | /**
446 | * 验证的文件必须具有与列出的其中一个扩展名相对应的 MIME 类型
447 | * ['photo', 'mimes', 'jpeg,bmp,png']
448 | *
449 | * @param string $field
450 | * @param array|string|null $types
451 | * return bool
452 | *
453 | * @todo
454 | */
455 | public function mimesValidator(string $field, array|string $types = null): void
456 | {
457 | }
458 |
459 | /*******************************************************************************
460 | * Field compare validators
461 | ******************************************************************************/
462 |
463 | /**
464 | * 字段值比较:当前字段值是否与给定的字段值相同
465 | *
466 | * @param mixed $val
467 | * @param string $compareField
468 | *
469 | * @return bool
470 | */
471 | public function compareValidator(mixed $val, string $compareField): bool
472 | {
473 | return $compareField && ($val === $this->getByPath($compareField));
474 | }
475 |
476 | public function eqFieldValidator($val, string $compareField): bool
477 | {
478 | return $this->compareValidator($val, $compareField);
479 | }
480 |
481 | /**
482 | * 字段值比较:当前字段值是否与给定的字段值不相同
483 | *
484 | * @param mixed $val
485 | * @param string $compareField
486 | *
487 | * @return bool
488 | */
489 | public function neqFieldValidator(mixed $val, string $compareField): bool
490 | {
491 | return $compareField && ($val !== $this->getByPath($compareField));
492 | }
493 |
494 | /**
495 | * 字段值比较:当前字段值 要小于 给定字段的值
496 | *
497 | * @param int|string $val
498 | * @param string $compareField
499 | *
500 | * @return bool
501 | */
502 | public function ltFieldValidator(int|string $val, string $compareField): bool
503 | {
504 | $maxVal = $this->getByPath($compareField);
505 |
506 | if ($maxVal === null) {
507 | return false;
508 | }
509 |
510 | return Validators::lt($val, $maxVal);
511 | }
512 |
513 | /**
514 | * 字段值比较:当前字段值 要小于等于 给定字段的值
515 | *
516 | * @param int|string $val
517 | * @param string $compareField
518 | *
519 | * @return bool
520 | */
521 | public function lteFieldValidator(int|string $val, string $compareField): bool
522 | {
523 | $maxVal = $this->getByPath($compareField);
524 |
525 | if ($maxVal === null) {
526 | return false;
527 | }
528 |
529 | return Validators::lte($val, $maxVal);
530 | }
531 |
532 | /**
533 | * 字段值比较:当前字段值 要大于 给定字段的值
534 | *
535 | * @param int|string $val
536 | * @param string $compareField
537 | *
538 | * @return bool
539 | */
540 | public function gtFieldValidator(int|string $val, string $compareField): bool
541 | {
542 | $minVal = $this->getByPath($compareField);
543 |
544 | if ($minVal === null) {
545 | return false;
546 | }
547 |
548 | return Validators::gt($val, $minVal);
549 | }
550 |
551 | /**
552 | * 字段值比较:当前字段值 要大于等于 给定字段的值
553 | *
554 | * @param int|string $val
555 | * @param string $compareField
556 | *
557 | * @return bool
558 | */
559 | public function gteFieldValidator(int|string $val, string $compareField): bool
560 | {
561 | $minVal = $this->getByPath($compareField);
562 |
563 | if ($minVal === null) {
564 | return false;
565 | }
566 |
567 | return Validators::gte($val, $minVal);
568 | }
569 |
570 | /**
571 | * 验证的 字段值 必须存在于另一个字段(anotherField)的值中。
572 | *
573 | * @param mixed $val
574 | * @param string $anotherField
575 | *
576 | * @return bool
577 | */
578 | public function inFieldValidator(mixed $val, string $anotherField): bool
579 | {
580 | if ($anotherField && $dict = $this->getByPath($anotherField)) {
581 | return Validators::in($val, $dict);
582 | }
583 |
584 | return false;
585 | }
586 |
587 | /**
588 | * 比较两个日期字段的 间隔天数 是否符合要求
589 | *
590 | * @param string $val
591 | * @param string $compareField
592 | * @param int $expected
593 | * @param string $op
594 | *
595 | * @todo
596 | */
597 | public function intervalDayValidator(string $val, string $compareField, int $expected, string $op = '>='): void
598 | {
599 | }
600 |
601 | /**
602 | * 对数组中的每个值都应用给定的验证器,并且要全部通过
603 | * `['foo.*.id', 'each', 'number']`
604 | * `['goods.*', 'each', 'string']`
605 | *
606 | * @param array $values
607 | * @param mixed ...$args
608 | * - string|\Closure $validator
609 | * - ... args for $validator
610 | *
611 | * @return bool
612 | * @throws InvalidArgumentException
613 | */
614 | public function eachValidator(array $values, ...$args): bool
615 | {
616 | if (!$validator = array_shift($args)) {
617 | throw new InvalidArgumentException('must setting a validator for \'each\' validate.');
618 | }
619 |
620 | foreach ($values as $value) {
621 | $passed = false;
622 |
623 | if (is_object($validator) && method_exists($validator, '__invoke')) {
624 | $passed = $validator($value, ...$args);
625 | } elseif (is_string($validator)) {
626 | // special for required
627 | if ('required' === $validator) {
628 | $passed = !Validators::isEmpty($value);
629 | } elseif (isset($this->_validators[$validator])) {
630 | $callback = $this->_validators[$validator];
631 | $passed = $callback($value, ...$args);
632 | } elseif (method_exists($this, $method = $validator . 'Validator')) {
633 | $passed = $this->$method($value, ...$args);
634 | } elseif (method_exists(Validators::class, $validator)) {
635 | $passed = Validators::$validator($value, ...$args);
636 |
637 | // it is function name
638 | } elseif (function_exists($validator)) {
639 | $passed = $validator($value, ...$args);
640 | } else {
641 | throw new InvalidArgumentException("The validator [$validator] don't exists!");
642 | }
643 | }
644 |
645 | if (!$passed) {
646 | return false;
647 | }
648 | }
649 |
650 | return true;
651 | }
652 |
653 | /*******************************************************************************
654 | * getter/setter/helper
655 | ******************************************************************************/
656 |
657 | /**
658 | * @param string $name
659 | *
660 | * @return bool
661 | */
662 | public static function isCheckFile(string $name): bool
663 | {
664 | return str_contains(Helper::$fileValidators, '|' . $name . '|');
665 | }
666 |
667 | /**
668 | * @param string $name
669 | * @param array $args
670 | * @return bool
671 | */
672 | public static function isCheckRequired(string $name, array $args = []): bool
673 | {
674 | // the $arg.0 is validator name.
675 | $name = $name === 'each' ? ((string)($args[0] ?? '')) : $name;
676 |
677 | return str_starts_with($name, 'required');
678 | }
679 |
680 | /**
681 | * @param string $field
682 | *
683 | * @return array|null
684 | */
685 | public function getUploadedFile(string $field): ?array
686 | {
687 | return $this->uploadedFiles[$field] ?? null;
688 | }
689 |
690 | /**
691 | * @return array
692 | */
693 | public function getUploadedFiles(): array
694 | {
695 | return $this->uploadedFiles;
696 | }
697 |
698 | /**
699 | * @param array $uploadedFiles
700 | *
701 | * @return static
702 | */
703 | public function setUploadedFiles(array $uploadedFiles): static
704 | {
705 | $this->uploadedFiles = $uploadedFiles;
706 | return $this;
707 | }
708 | }
709 |
--------------------------------------------------------------------------------
/src/Validation.php:
--------------------------------------------------------------------------------
1 | validate();
23 | *
24 | * $vd->isFail();// bool
25 | * $vd->firstError(); // get first error message.
26 | * $vd->isOk();// bool
27 | */
28 | class Validation extends AbstractValidation
29 | {
30 | /* examples:
31 | public function rules()
32 | {
33 | return [
34 | ['fields', 'validator', arg0, arg1, something ...]
35 | ['tagId,userId,name,email,freeTime', 'required'],
36 | ['userId', 'number'],
37 | ];
38 | }
39 | */
40 |
41 | /**
42 | * @param string $key
43 | * @param mixed|null $value
44 | *
45 | * @return mixed
46 | */
47 | public function get(string $key, $value = null): mixed
48 | {
49 | return $this->traitGet($key, $value);
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/ValidationInterface.php:
--------------------------------------------------------------------------------
1 | validate($value, $data);
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/Validator/GlobalMessage.php:
--------------------------------------------------------------------------------
1 | [
19 | '{attr} must be an integer!',
20 | '{attr} must be an integer and minimum value is {min}',
21 | '{attr} must be an integer and value range {min} ~ {max}',
22 | ],
23 | // 'num'
24 | 'number' => [
25 | '{attr} must be an integer greater than 0!',
26 | '{attr} must be an integer and minimum value is {min}',
27 | '{attr} must be an integer and in the range {min} ~ {max}',
28 | ],
29 | // 'bool', 'boolean',
30 | 'boolean' => '{attr} must be an boolean!',
31 | 'float' => '{attr} must be an float!',
32 | 'url' => '{attr} must be a URL address!',
33 | 'email' => '{attr} is not a email address!',
34 | 'date' => '{attr} is not a date format!',
35 | 'dateFormat' => '{attr} is not in a {value0} date format!',
36 | 'ip' => '{attr} is not IP address!',
37 | 'ipv4' => '{attr} is not a IPv4 address!',
38 | 'ipv6' => '{attr} is not a IPv6 address!',
39 | 'required' => 'parameter {attr} is required!',
40 | 'requiredIf' => 'parameter {attr} is required!',
41 | 'length' => [
42 | '{attr} length validation is not through!',
43 | '{attr} must be an string/array and minimum length is {min}',
44 | '{attr} must be an string/array and length range {min} ~ {max}',
45 | ],
46 | // 'range', 'between'
47 | 'size' => [
48 | '{attr} size validation is not through!',
49 | '{attr} must be an integer/string/array and minimum value/length is {min}',
50 | // '{attr} must be an integer/string/array and value/length range {min} ~ {max}',
51 | '{attr} must be in the range {min} ~ {max}',
52 | ],
53 |
54 | // 'lengthEq', 'sizeEq'
55 | 'fixedSize' => '{attr} length must is {value0}',
56 |
57 | 'eq' => '{attr} must be equals to {value0}',
58 | // 'different'
59 | 'ne' => '{attr} cannot be equals to {value0}',
60 | 'min' => '{attr} minimum boundary is {value0}',
61 | 'max' => '{attr} maximum boundary is {value0}',
62 | 'lt' => '{attr} value must be less than {value0}',
63 | 'lte' => '{attr} value must be less than or equals to {value0}',
64 | 'gt' => '{attr} value must be greater than {value0}',
65 | 'gte' => '{attr} value must be greater than or equals to {value0}',
66 |
67 | // field compare
68 | 'eqField' => '{attr} value must be equals to {value0}',
69 | 'neqField' => '{attr} value cannot be equals to {value0}',
70 | 'ltField' => '{attr} value must be less than {value0}',
71 | 'lteField' => '{attr} value must be less than or equals to {value0}',
72 | 'gtField' => '{attr} value must be greater than {value0}',
73 | 'gteField' => '{attr} value must be greater than or equals to {value0}',
74 | 'inField' => '{attr} value must be exists in {value0}',
75 |
76 | // 'in', 'enum',
77 | 'enum' => '{attr} must in ({value0})',
78 | 'notIn' => '{attr} cannot in ({value0})',
79 |
80 | 'string' => [
81 | '{attr} must be a string',
82 | '{attr} must be a string and minimum length be {min}',
83 | '{attr} must be a string and length range must be {min} ~ {max}',
84 | ],
85 |
86 | // 'regex', 'regexp',
87 | 'regexp' => '{attr} does not match the {value0} conditions',
88 |
89 | 'mustBe' => '{attr} must be equals to {value0}',
90 | 'notBe' => '{attr} can not be equals to {value0}',
91 |
92 | 'compare' => '{attr} must be equals to {value0}',
93 | 'same' => '{attr} must be equals to {value0}',
94 |
95 | 'isArray' => '{attr} must be an array',
96 | 'isMap' => '{attr} must be an array and is key-value format',
97 | 'isList' => '{attr} must be an array of nature',
98 | 'intList' => '{attr} must be an array and value is all integers',
99 | 'numList' => '{attr} must be an array and value is all numbers',
100 | 'strList' => '{attr} must be an array and value is all strings',
101 | 'arrList' => '{attr} must be an array and value is all arrays',
102 |
103 | 'each' => '{attr} each value must be through the "{value0}" verify',
104 | 'hasKey' => '{attr} must be contains the key {value0}',
105 | 'distinct' => 'there should not be duplicate keys in the {attr}',
106 |
107 | 'json' => '{attr} must be an json string',
108 |
109 | 'file' => '{attr} must be an uploaded file',
110 | 'image' => '{attr} must be an uploaded image file',
111 |
112 | 'callback' => '{attr} don\'t pass the test and verify!',
113 | '__default' => '{attr} validation is not through!',
114 | ];
115 |
116 | /**
117 | * @param string $key
118 | *
119 | * @return string|array
120 | */
121 | public static function get(string $key): array|string
122 | {
123 | return self::$messages[$key] ?? '';
124 | }
125 |
126 | /**
127 | * @param string $key
128 | * @param array|string $msg
129 | */
130 | public static function set(string $key, array|string $msg): void
131 | {
132 | if ($key && $msg) {
133 | self::$messages[$key] = $msg;
134 | }
135 | }
136 |
137 | /**
138 | * @param string $key
139 | *
140 | * @return bool
141 | */
142 | public static function has(string $key): bool
143 | {
144 | return isset(self::$messages[$key]);
145 | }
146 |
147 | /**
148 | * @return string
149 | */
150 | public static function getDefault(): string
151 | {
152 | return self::$messages['__default'];
153 | }
154 |
155 | /**
156 | * @param array $messages
157 | */
158 | public static function setMessages(array $messages): void
159 | {
160 | foreach ($messages as $key => $value) {
161 | self::set($key, $value);
162 | }
163 | }
164 |
165 | /**
166 | * @return array
167 | */
168 | public static function getMessages(): array
169 | {
170 | return self::$messages;
171 | }
172 | }
173 |
--------------------------------------------------------------------------------
/src/Validator/UserValidators.php:
--------------------------------------------------------------------------------
1 | $validator) {
73 | self::set($name, $validator);
74 | }
75 | }
76 |
77 | /**
78 | * @return array
79 | */
80 | public static function getValidators(): array
81 | {
82 | return self::$validators;
83 | }
84 |
85 | /**
86 | * @param string $name
87 | */
88 | public static function remove(string $name): void
89 | {
90 | if (isset(self::$validators[$name])) {
91 | unset(self::$validators[$name]);
92 | }
93 | }
94 |
95 | /**
96 | * clear all validators
97 | */
98 | public static function removeAll(): void
99 | {
100 | self::$validators = [];
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/src/Validator/ValidatorInterface.php:
--------------------------------------------------------------------------------
1 | data = $data;
26 |
27 | return $this;
28 | }
29 |
30 | public function create(): bool|int
31 | {
32 | if ($this->validate()->isFail()) {
33 | return false;
34 | }
35 |
36 | // return $this->db->insert($this->getSafeData());
37 | return 1;
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/test/Example/FieldExample.php:
--------------------------------------------------------------------------------
1 | ['user', 'pwd', 'code'],
33 | 'update' => ['user', 'pwd'],
34 | ];
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/test/Example/PageRequest.php:
--------------------------------------------------------------------------------
1 | 4, 'max' => 567], // 4<= tagId <=567
23 | ['title', 'min', 'min' => 40],
24 | ['freeTime', 'number', 'msg' => '{attr} is require number!'],
25 | [
26 | 'test',
27 | 'number',
28 | 'when' => function ($data) {
29 | return isset($data['status']) && $data['status'] > 2;
30 | }
31 | ],
32 | ['userId', 'number', 'on' => 'other'],
33 | // ['userId', function($value){ return false;}],
34 | ];
35 | }
36 |
37 | public function translates(): array
38 | {
39 | return [
40 | 'userId' => '用户Id',
41 | ];
42 | }
43 |
44 | /**
45 | * custom validator message
46 | *
47 | * @return array
48 | */
49 | public function messages(): array
50 | {
51 | return [
52 | 'required' => '{attr} 是必填项。',
53 | ];
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/test/Example/RuleExample.php:
--------------------------------------------------------------------------------
1 | 234,
20 | 'userId' => 'is not an integer',
21 | 'tagId' => '234535',
22 | // 'freeTime' => '1456767657', // filed not exists
23 | 'note' => '',
24 | 'name' => 'Ajohn',
25 | 'status' => 2,
26 | 'existsField' => 'test',
27 | 'passwd' => 'password',
28 | 'repasswd' => 'repassword',
29 | 'insertTime' => '1456767657',
30 | 'goods' => [
31 | 'apple' => 34,
32 | 'pear' => 50,
33 | ],
34 | ];
35 |
36 | public function testRuleCollectError(): void
37 | {
38 | $rv = FieldValidation::make(['name' => 'inhere'], [
39 | []
40 | ]);
41 | try {
42 | $rv->validate();
43 | } catch (Throwable $e) {
44 | $this->assertSame('Please setting the field(string) to wait validate! position: rule[0]', $e->getMessage());
45 | }
46 |
47 | $rv = FieldValidation::make(['name' => 'inhere'], [
48 | ['name']
49 | ]);
50 | try {
51 | $rv->validate();
52 | } catch (Throwable $e) {
53 | $this->assertSame(
54 | 'Please setting the validator(s) for validate field! position: rule[1]',
55 | $e->getMessage()
56 | );
57 | }
58 | }
59 |
60 | public function testValidateField(): void
61 | {
62 | $rules = [
63 | ['freeTime', 'required'],
64 | ['userId', 'required|int'],
65 | ['tagId', 'size:0,50'],
66 | ['status', 'enum:1,2'],
67 | ['goods.pear', 'max:30'],
68 | ];
69 |
70 | $v = FieldValidation::make($this->data, $rules);
71 | $v->setMessages([
72 | 'freeTime.required' => 'freeTime is required!!!!'
73 | ])->validate([], false);
74 |
75 | $this->assertFalse($v->isOk());
76 | $this->assertTrue($v->failed());
77 |
78 | $errors = $v->getErrors();
79 | $this->assertNotEmpty($errors);
80 |
81 | $this->assertCount(4, $errors);
82 | $this->assertSame('freeTime is required!!!!', $v->getErrors('freeTime')[0]);
83 |
84 | $v = FieldValidation::check($this->data, [
85 | ['goods.pear', 'required|int|min:30|max:60']
86 | ]);
87 | $this->assertTrue($v->isOk());
88 |
89 | $v = FieldValidation::check($this->data, [
90 | ['userId', 'required|int'],
91 | ['userId', 'min:1'],
92 | ]);
93 |
94 | $this->assertFalse($v->isOk());
95 | $errors = $v->getErrors();
96 | $this->assertNotEmpty($errors);
97 | $this->assertCount(1, $errors);
98 |
99 | $v = FieldValidation::check([
100 | 'title' => 'hello',
101 | ], [
102 | ['title', 'required|string:2,8']
103 | ]);
104 | $this->assertTrue($v->isOk());
105 |
106 | $v = FieldValidation::check([
107 | 'title' => 'hello',
108 | ], [
109 | ['title', 'required|string:1,3']
110 | ]);
111 | $this->assertTrue($v->isFail());
112 | $this->assertSame('title must be a string and length range must be 1 ~ 3', $v->firstError());
113 | }
114 |
115 | public function testOnScene(): void
116 | {
117 | $data = [
118 | 'user' => 'inhere',
119 | 'pwd' => '123456',
120 | 'code' => '1234',
121 | ];
122 |
123 | $v = FieldValidation::make($data, [
124 | ['user', 'required|string', 'on' => 's1'],
125 | ['code', 'required|int', 'filter' => 'int', 'on' => 's2'],
126 | ]);
127 |
128 | $v->atScene('s1')->validate();
129 |
130 | $this->assertCount(1, $v->getUsedRules());
131 | }
132 |
133 | public function testScenarios(): void
134 | {
135 | $data = [
136 | 'user' => 'inhere',
137 | 'pwd' => '123456',
138 | 'code' => '1234',
139 | ];
140 |
141 | $v = FieldExample::quick($data, 'create')->validate();
142 | $this->assertTrue($v->isOk());
143 | $this->assertEmpty($v->getErrors());
144 |
145 | $data = [
146 | 'user' => 'inhere',
147 | 'pwd' => '123456',
148 | 'code' => '12345',
149 | ];
150 |
151 | $v = FieldExample::quick($data, 'create')->validate();
152 | $this->assertFalse($v->isOk());
153 | $this->assertEquals('code length must is 4', $v->firstError());
154 |
155 | $v = FieldExample::quick($data, 'update')->validate();
156 | $this->assertTrue($v->isOk());
157 | $this->assertEmpty($v->getErrors());
158 | }
159 |
160 | /**
161 | * @link https://github.com/inhere/php-validate/issues/22
162 | */
163 | public function testIssues22(): void
164 | {
165 | $rs = [
166 | ['id', 'required'],
167 | ['name', 'required|string:5,10', 'msg' => '5~10位的字符串'],
168 | ['sex', 'required|enum:0,1'],
169 | ['age', 'requiredIf:sex,0|int']
170 | ];
171 |
172 | $v = FV::check([
173 | 'id' => 1,
174 | 'name' => '12345',
175 | 'sex' => 0,
176 | 'age' => 25,
177 | ], $rs);
178 |
179 | $this->assertTrue($v->isOk());
180 |
181 | $v = FV::check([
182 | 'id' => 1,
183 | 'name' => '12345',
184 | 'sex' => 1,
185 | // 'age' => 25,
186 | ], $rs);
187 |
188 | $this->assertTrue($v->isOk());
189 |
190 | $v = FV::check([
191 | 'id' => 1,
192 | 'name' => '12345',
193 | 'sex' => 0,
194 | // 'age' => 25,
195 | // 'age' => 'string',
196 | ], $rs);
197 |
198 | $this->assertFalse($v->isOk());
199 | $this->assertSame('parameter age is required!', $v->firstError());
200 |
201 | $v = FV::check([
202 | 'id' => 1,
203 | 'name' => '12345',
204 | 'sex' => 0,
205 | 'age' => 'string',
206 | ], $rs);
207 |
208 | $this->assertFalse($v->isOk());
209 | $this->assertSame('age must be an integer!', $v->firstError());
210 | }
211 |
212 | /**
213 | * @link https://github.com/inhere/php-validate/issues/36
214 | */
215 | public function testIssues36(): void
216 | {
217 | $params = [];
218 |
219 | $v = FieldValidation::check($params, [
220 | ['owner', 'required', 'msg' => ['owner' => 'owner 缺失']],
221 | ]);
222 |
223 | $this->assertTrue($v->isFail());
224 | //$this->assertSame('parameter owner is required!', $v->firstError());
225 |
226 | $v = FieldValidation::check($params, [
227 | ['owner', 'required', 'msg' => ['required' => 'owner 缺失']],
228 | ]);
229 |
230 | $this->assertTrue($v->isFail());
231 | $this->assertSame('owner 缺失', $v->firstError());
232 | }
233 |
234 | /**
235 | * @link https://github.com/inhere/php-validate/issues/55
236 | */
237 | public function testIssues55(): void
238 | {
239 | $data = ['title' => '', 'name' => ''];
240 | $msg = ['title' => '标题不能为空。', 'name' => '姓名不能为空。'];
241 |
242 | $validator = \Inhere\Validate\Validation::make($data, [
243 | ['title,name', 'required', 'msg' => $msg],
244 | ])->validate([], false);
245 |
246 | $this->assertTrue($validator->isFail());
247 | $this->assertSame(
248 | [[
249 | 'name' => 'title',
250 | 'msg' => $msg['title'],
251 | ], [
252 | 'name' => 'name',
253 | 'msg' => $msg['name'],
254 | ]],
255 | $validator->getErrors()
256 | );
257 | }
258 | }
259 |
--------------------------------------------------------------------------------
/test/Filter/FiltersTest.php:
--------------------------------------------------------------------------------
1 | true,
22 | 'yes' => true,
23 | 'Yes' => true,
24 | 'YEs' => true,
25 | 'true' => true,
26 | 'True' => true,
27 | '0' => false,
28 | 'no' => false,
29 | 'off' => false,
30 | 'false' => false,
31 | 'False' => false,
32 | null => false,
33 | ];
34 |
35 | foreach ($samples as $sample => $expected) {
36 | $this->assertSame($expected, Filters::bool($sample));
37 | }
38 |
39 | $this->assertFalse(Filters::bool([]));
40 | }
41 |
42 | public function testAliases(): void
43 | {
44 | $this->assertTrue(Filters::hasAlias('str2list'));
45 | $this->assertSame('explode', Filters::realName('str2list'));
46 |
47 | $this->assertFalse(Filters::hasAlias('not-exist'));
48 | $this->assertSame('not-exist', Filters::realName('not-exist'));
49 |
50 | $this->assertFalse(Filters::hasAlias('new-key'));
51 | Filters::setAliases([
52 | 'new-key' => 'new-val',
53 | ]);
54 | $this->assertTrue(Filters::hasAlias('new-key'));
55 | $this->assertSame('new-val', Filters::realName('new-key'));
56 | }
57 |
58 | public function testInteger(): void
59 | {
60 | $this->assertSame(Filters::integer('456'), 456);
61 | $this->assertSame(Filters::integer('4df5dg6'), 456);
62 |
63 | $this->assertSame(Filters::integer(['34', '67gh']), [34, 67]);
64 | }
65 |
66 | public function testAbs(): void
67 | {
68 | $this->assertSame(Filters::abs('456'), 456);
69 | $this->assertSame(Filters::abs(-45), 45);
70 | }
71 |
72 | public function testFloat(): void
73 | {
74 | // $this->assertSame(FilterList::float('4.45'), 4.45);
75 | $this->assertSame(Filters::float(45.78), 45.78);
76 | $this->assertSame(Filters::float(-45.78), -45.78);
77 |
78 | $this->assertSame(Filters::float(45.78678, 2), 45.79);
79 | $this->assertSame(Filters::float(457, 2), 457.00);
80 | }
81 |
82 | public function testString(): void
83 | {
84 | self::assertSame('1', Filters::string(1));
85 | self::assertSame('tom', Filters::string('tom'));
86 | self::assertSame('tom', Filters::stripped('tom'));
87 | self::assertSame('abc&', Filters::string('abc&'));
88 | self::assertSame(['abc&', '1'], Filters::string(['abc&', 1]));
89 | }
90 |
91 | public function testStringFilter(): void
92 | {
93 | // quotes
94 | $this->assertSame("O\'Reilly?", Filters::quotes("O'Reilly?"));
95 |
96 | // email
97 | $this->assertSame('', Filters::email(''));
98 |
99 | // url
100 | $this->assertSame('', Filters::url(''));
101 | $this->assertSame('abc/hi', Filters::url('abc/hi'));
102 |
103 | // unsafeRaw
104 | $this->assertSame('abc/hi', Filters::unsafeRaw('abc/hi'));
105 |
106 | // specialChars
107 | $this->assertSame('<a>link</a> hello, a & b', Filters::escape('link hello, a & b'));
108 | $this->assertSame('abc', Filters::specialChars('abc'));
109 |
110 | // fullSpecialChars
111 | $this->assertSame('hello, a & b', Filters::fullSpecialChars('hello, a & b'));
112 | }
113 |
114 | public function testNl2br(): void
115 | {
116 | $this->assertSame('a text text' => 'text',
219 | '
b', Filters::nl2br("a\nb"));
117 | $this->assertSame('a
b', Filters::nl2br("a\r\nb"));
118 | }
119 |
120 | public function testStringCut(): void
121 | {
122 | $this->assertSame('cDE', Filters::stringCute('abcDEFgh', 2, 3));
123 | $this->assertSame('abcDEFgh', Filters::cut('abcDEFgh'));
124 | $this->assertSame('cDEFgh', Filters::cut('abcDEFgh', 2));
125 | $this->assertSame('abcDEFgh', Filters::cut('abcDEFgh', 0, 12));
126 | }
127 |
128 | public function testClearXXX(): void
129 | {
130 | // clearSpace
131 | $samples = ['abc ', ' abc ', 'a bc', 'a b c', ' a b c'];
132 |
133 | foreach ($samples as $sample) {
134 | $this->assertSame('abc', Filters::clearSpace($sample));
135 | }
136 |
137 | // clearNewline
138 | self::assertSame('ab', Filters::clearNewline("a\nb"));
139 | self::assertSame('ab', Filters::clearNewline("a\r\nb"));
140 | }
141 |
142 | public function testTrim(): void
143 | {
144 | $this->assertEquals(Filters::trim(' test '), 'test');
145 | $this->assertEquals(Filters::trim([' test ', 'a ']), ['test', 'a']);
146 | }
147 |
148 | public function testChangeCase(): void
149 | {
150 | // lowercase
151 | $this->assertSame('test', Filters::lowercase('Test'));
152 | $this->assertSame('23', Filters::lowercase(23));
153 |
154 | // uppercase
155 | $this->assertSame('23', Filters::uppercase(23));
156 | $this->assertSame(Filters::upper('test'), 'TEST');
157 | $this->assertSame(Filters::uppercase('Test'), 'TEST');
158 |
159 | // ucfirst
160 | $this->assertSame('Abc', Filters::ucfirst('abc'));
161 | $this->assertSame('', Filters::ucfirst(''));
162 |
163 | // ucwords
164 | $this->assertSame('Hello World', Filters::ucwords('hello world'));
165 | $this->assertSame('', Filters::ucwords(''));
166 |
167 | // snake case
168 | $this->assertSame('hello_world', Filters::snake('HelloWorld'));
169 | $this->assertSame('hello-world', Filters::snake('HelloWorld', '-'));
170 | $this->assertSame('', Filters::snake(''));
171 |
172 | // camel case
173 | $this->assertSame('helloWorld', Filters::camel('hello_world'));
174 | $this->assertSame('HelloWorld', Filters::camel('hello_world', true));
175 | $this->assertSame('', Filters::camel(''));
176 | }
177 |
178 | public function testTime(): void
179 | {
180 | $this->assertSame(1563811200, Filters::timestamp('2019-07-23'));
181 | $this->assertSame(0, Filters::timestamp(''));
182 | $this->assertSame(0, Filters::timestamp('invalid'));
183 | }
184 |
185 | public function testStr2list(): void
186 | {
187 | $samples = [
188 | '0,23' => ['0', '23'],
189 | 'a,b,c,' => ['a', 'b', 'c'],
190 | 'a, b ,c,' => ['a', 'b', 'c'],
191 | ' a, b , c' => ['a', 'b', 'c'],
192 | ' a,, b ,, c' => ['a', 'b', 'c'],
193 | ];
194 |
195 | foreach ($samples as $sample => $expected) {
196 | $this->assertSame($expected, Filters::str2array($sample));
197 | $this->assertSame($expected, Filters::explode($sample));
198 | }
199 |
200 | $this->assertSame([], Filters::explode(''));
201 | $this->assertSame([], Filters::str2list(' , '));
202 | }
203 |
204 | public function testUnique(): void
205 | {
206 | $this->assertSame([1], Filters::unique(1));
207 | $this->assertSame([1], Filters::unique([1, 1]));
208 | $this->assertSame(['a', 'b'], Filters::unique(['a', 'b', 'a']));
209 | $this->assertSame(['a', 2 => 'b'], Filters::unique(['a', 'a', 'b', 'a']));
210 | }
211 |
212 | public function testEncodeOrClearTag(): void
213 | {
214 | // clearTags
215 | $samples = [
216 | '' => '',
217 | '