├── tests
├── Cases
│ ├── fixtures
│ │ ├── empty.png
│ │ ├── image.png
│ │ ├── image2.png
│ │ ├── image3.png
│ │ ├── image.svg
│ │ └── Values.php
│ ├── AbstractTestCase.php
│ ├── ValidationRuleTest.php
│ ├── ValidationRequiredIfTest.php
│ ├── ValidationNotInRuleTest.php
│ ├── ValidationDimensionsRuleTest.php
│ ├── ValidationInRuleTest.php
│ ├── ValidationAddFailureTest.php
│ ├── ValidationUniqueRuleTest.php
│ ├── ValidationDatabasePresenceVerifierTest.php
│ ├── ValidationFactoryTest.php
│ └── ValidationExistsRuleTest.php
├── ci.ini
├── swoole.install.sh
└── bootstrap.php
├── .phpstorm.meta.php
├── .gitignore
├── src
├── Contracts
│ ├── Validation
│ │ ├── ImplicitRule.php
│ │ ├── ValidatesWhenResolved.php
│ │ ├── Rule.php
│ │ ├── Factory.php
│ │ └── Validator.php
│ └── Support
│ │ ├── MessageProvider.php
│ │ └── MessageBag.php
├── UnauthorizedException.php
├── Rules
│ ├── Exists.php
│ ├── RequiredIf.php
│ ├── NotIn.php
│ ├── In.php
│ ├── Unique.php
│ ├── Dimensions.php
│ └── DatabaseRule.php
├── Request
│ ├── stubs
│ │ └── request.stub
│ ├── RequestCommand.php
│ └── FormRequest.php
├── DatabasePresenceVerifierFactory.php
├── PresenceVerifierInterface.php
├── ConfigProvider.php
├── ValidatorFactory.php
├── ValidationExceptionHandler.php
├── ClosureValidationRule.php
├── ValidatesWhenResolvedTrait.php
├── Rule.php
├── ValidationData.php
├── ValidationException.php
├── DatabasePresenceVerifier.php
├── Middleware
│ └── ValidationMiddleware.php
├── ValidationRuleParser.php
├── Factory.php
├── Support
│ └── MessageBag.php
├── Concerns
│ ├── FormatsMessages.php
│ └── ReplacesAttributes.php
└── Validator.php
├── phpunit.xml
├── .travis.yml
├── composer.json
├── README.md
└── .php_cs
/tests/Cases/fixtures/empty.png:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/Cases/fixtures/image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chunhei2008/hyperf-validation/HEAD/tests/Cases/fixtures/image.png
--------------------------------------------------------------------------------
/tests/Cases/fixtures/image2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chunhei2008/hyperf-validation/HEAD/tests/Cases/fixtures/image2.png
--------------------------------------------------------------------------------
/tests/Cases/fixtures/image3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chunhei2008/hyperf-validation/HEAD/tests/Cases/fixtures/image3.png
--------------------------------------------------------------------------------
/tests/ci.ini:
--------------------------------------------------------------------------------
1 | [opcache]
2 | opcache.enable_cli=1
3 |
4 | [redis]
5 | extension = "redis.so"
6 |
7 | [swoole]
8 | extension = "swoole.so"
9 |
--------------------------------------------------------------------------------
/tests/Cases/fixtures/image.svg:
--------------------------------------------------------------------------------
1 |
3 |
--------------------------------------------------------------------------------
/.phpstorm.meta.php:
--------------------------------------------------------------------------------
1 | table,
18 | $this->column,
19 | $this->formatWheres()
20 | ), ',');
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/Contracts/Validation/Rule.php:
--------------------------------------------------------------------------------
1 | assertSame('regex:/^([0-9\s\-\+\(\)]*)$/', $c);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/Contracts/Support/MessageProvider.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * Date: 2019-07-25 18:32
8 | * Copyright: (C) 2014, Guangzhou YIDEJIA Network Technology Co., Ltd.
9 | */
10 |
11 | namespace Chunhei2008\Hyperf\Validation\Contracts\Support;
12 |
13 |
14 | use Chunhei2008\Hyperf\Validation\Contracts\Support\MessageBag;
15 |
16 | interface MessageProvider
17 | {
18 | /**
19 | * Get the messages for the instance.
20 | *
21 | * @return MessageBag
22 | */
23 | public function getMessageBag();
24 | }
25 |
--------------------------------------------------------------------------------
/src/Request/stubs/request.stub:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * Date: 2019/7/26 01:50
8 | * Copyright: (C) 2014, Guangzhou YIDEJIA Network Technology Co., Ltd.
9 | */
10 |
11 | namespace Chunhei2008\Hyperf\Validation;
12 |
13 |
14 | use Hyperf\Database\ConnectionResolverInterface;
15 | use Psr\Container\ContainerInterface;
16 |
17 | class DatabasePresenceVerifierFactory
18 | {
19 | public function __invoke(ContainerInterface $container)
20 | {
21 | $db = $container->get(ConnectionResolverInterface::class);
22 |
23 | $presenceVerifier = make(DatabasePresenceVerifier::class, compact('db'));
24 |
25 | return $presenceVerifier;
26 |
27 | }
28 | }
--------------------------------------------------------------------------------
/tests/Cases/ValidationRequiredIfTest.php:
--------------------------------------------------------------------------------
1 | assertEquals('required', (string) $rule);
17 |
18 | $rule = new RequiredIf(function () {
19 | return false;
20 | });
21 |
22 | $this->assertEquals('', (string) $rule);
23 |
24 | $rule = new RequiredIf(true);
25 |
26 | $this->assertEquals('required', (string) $rule);
27 |
28 | $rule = new RequiredIf(false);
29 |
30 | $this->assertEquals('', (string) $rule);
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/Rules/RequiredIf.php:
--------------------------------------------------------------------------------
1 | condition = $condition;
23 | }
24 |
25 | /**
26 | * Convert the rule to a validation string.
27 | *
28 | * @return string
29 | */
30 | public function __toString()
31 | {
32 | if (is_callable($this->condition)) {
33 | return call_user_func($this->condition) ? 'required' : '';
34 | }
35 |
36 | return $this->condition ? 'required' : '';
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/tests/Cases/ValidationNotInRuleTest.php:
--------------------------------------------------------------------------------
1 | assertEquals('not_in:"Laravel","Framework","PHP"', (string) $rule);
16 |
17 | $rule = Rule::notIn([1, 2, 3, 4]);
18 |
19 | $this->assertEquals('not_in:"1","2","3","4"', (string) $rule);
20 |
21 | $rule = Rule::notIn(collect([1, 2, 3, 4]));
22 |
23 | $this->assertEquals('not_in:"1","2","3","4"', (string) $rule);
24 |
25 | $rule = Rule::notIn('1', '2', '3', '4');
26 |
27 | $this->assertEquals('not_in:"1","2","3","4"', (string) $rule);
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/Rules/NotIn.php:
--------------------------------------------------------------------------------
1 | values = $values;
28 | }
29 |
30 | /**
31 | * Convert the rule to a validation string.
32 | *
33 | * @return string
34 | */
35 | public function __toString()
36 | {
37 | $values = array_map(function ($value) {
38 | return '"'.str_replace('"', '""', $value).'"';
39 | }, $this->values);
40 |
41 | return $this->rule.':'.implode(',', $values);
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 | ./tests/
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/src/PresenceVerifierInterface.php:
--------------------------------------------------------------------------------
1 | [
22 | \Chunhei2008\Hyperf\Validation\PresenceVerifierInterface::class => \Chunhei2008\Hyperf\Validation\DatabasePresenceVerifierFactory::class,
23 | \Chunhei2008\Hyperf\Validation\Contracts\Validation\Factory::class => \Chunhei2008\Hyperf\Validation\ValidatorFactory::class,
24 | ],
25 | 'scan' => [
26 | 'paths' => [
27 | __DIR__,
28 | ],
29 | ],
30 | ];
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/Rules/In.php:
--------------------------------------------------------------------------------
1 | values = $values;
28 | }
29 |
30 | /**
31 | * Convert the rule to a validation string.
32 | *
33 | * @return string
34 | *
35 | * @see \Chunhei2008\Hyperf\Validation\ValidationRuleParser::parseParameters
36 | */
37 | public function __toString()
38 | {
39 | $values = array_map(function ($value) {
40 | return '"'.str_replace('"', '""', $value).'"';
41 | }, $this->values);
42 |
43 | return $this->rule.':'.implode(',', $values);
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/ValidatorFactory.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * Date: 2019/7/26 01:31
8 | * Copyright: (C) 2014, Guangzhou YIDEJIA Network Technology Co., Ltd.
9 | */
10 |
11 | namespace Chunhei2008\Hyperf\Validation;
12 |
13 |
14 | use Chunhei2008\Hyperf\Translation\Contracts\Translator;
15 | use Hyperf\Database\ConnectionResolverInterface;
16 | use Psr\Container\ContainerInterface;
17 |
18 | class ValidatorFactory
19 | {
20 | public function __invoke(ContainerInterface $container)
21 | {
22 | $translator = $container->get(Translator::class);
23 |
24 | $validator = make(Factory::class, compact('translator','container'));
25 |
26 | if ($container->has(ConnectionResolverInterface::class) && $container->has(PresenceVerifierInterface::class)) {
27 | $presenceVerifier = $container->get(PresenceVerifierInterface::class);
28 | $validator->setPresenceVerifier($presenceVerifier);
29 | }
30 |
31 | return $validator;
32 | }
33 | }
--------------------------------------------------------------------------------
/src/Request/RequestCommand.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * Date: 2019-07-26 20:59
8 | * Copyright: (C) 2014, Guangzhou YIDEJIA Network Technology Co., Ltd.
9 | */
10 |
11 | namespace Chunhei2008\Hyperf\Validation\Request;
12 |
13 | use Hyperf\Command\Annotation\Command;
14 | use Hyperf\Devtool\Generator\GeneratorCommand;
15 |
16 |
17 | /**
18 | * Class RequestCommand
19 | * @package Chunhei2008\Hyperf\Validation\Request
20 | * @Command
21 | */
22 | class RequestCommand extends GeneratorCommand
23 | {
24 | public function __construct()
25 | {
26 | parent::__construct('gen:request');
27 | $this->setDescription('Create a new form request class');
28 | }
29 |
30 | protected function getStub(): string
31 | {
32 | return $this->getConfig()['stub'] ?? __DIR__ . '/stubs/request.stub';
33 | }
34 |
35 | protected function getDefaultNamespace(): string
36 | {
37 | return $this->getConfig()['namespace'] ?? 'App\\Requests';
38 | }
39 | }
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: php
2 |
3 | sudo: required
4 |
5 | matrix:
6 | include:
7 | - php: 7.2
8 | env: SW_VERSION="4.3.5"
9 | - php: 7.2
10 | env: SW_VERSION="4.4.0-beta"
11 | - php: 7.3
12 | env: SW_VERSION="4.3.5"
13 | - php: 7.3
14 | env: SW_VERSION="4.4.0-beta"
15 | - php: master
16 | env: SW_VERSION="4.3.5"
17 | - php: master
18 | env: SW_VERSION="4.4.0-beta"
19 |
20 | allow_failures:
21 | - php: master
22 |
23 | services:
24 | - mysql
25 | - redis
26 | - docker
27 |
28 | before_install:
29 | - export PHP_MAJOR="$(`phpenv which php` -r 'echo phpversion();' | cut -d '.' -f 1)"
30 | - export PHP_MINOR="$(`phpenv which php` -r 'echo phpversion();' | cut -d '.' -f 2)"
31 | - echo $PHP_MAJOR
32 | - echo $PHP_MINOR
33 |
34 | install:
35 | - cd $TRAVIS_BUILD_DIR
36 | - bash ./tests/swoole.install.sh
37 | - phpenv config-rm xdebug.ini || echo "xdebug not available"
38 | - phpenv config-add ./tests/ci.ini
39 |
40 | before_script:
41 | - cd $TRAVIS_BUILD_DIR
42 | - composer config -g process-timeout 900 && composer update
43 |
44 | script:
45 | - composer analyze
46 | - composer test --dev
--------------------------------------------------------------------------------
/src/ValidationExceptionHandler.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * Date: 2019-07-26 17:01
8 | * Copyright: (C) 2014, Guangzhou YIDEJIA Network Technology Co., Ltd.
9 | */
10 |
11 | namespace Chunhei2008\Hyperf\Validation;
12 |
13 |
14 | use Hyperf\ExceptionHandler\ExceptionHandler;
15 | use Hyperf\HttpMessage\Stream\SwooleStream;
16 | use Psr\Http\Message\ResponseInterface;
17 | use Throwable;
18 |
19 | class ValidationExceptionHandler extends ExceptionHandler
20 | {
21 | public function handle(Throwable $throwable, ResponseInterface $response)
22 | {
23 | if ($throwable instanceof ValidationException) {
24 | $data = json_encode([
25 | 'code' => $throwable->getCode(),
26 | 'message' => $throwable->validator->errors()->first(),
27 | ], JSON_UNESCAPED_UNICODE);
28 |
29 | $this->stopPropagation();
30 |
31 | return $response->withStatus(500)->withBody(new SwooleStream($data));
32 | }
33 |
34 | return $response;
35 | }
36 |
37 | public function isValid(Throwable $throwable): bool
38 | {
39 | return true;
40 | }
41 | }
--------------------------------------------------------------------------------
/tests/Cases/ValidationDimensionsRuleTest.php:
--------------------------------------------------------------------------------
1 | 100, 'min_height' => 100]);
14 |
15 | $this->assertEquals('dimensions:min_width=100,min_height=100', (string) $rule);
16 |
17 | $rule = Rule::dimensions()->width(200)->height(100);
18 |
19 | $this->assertEquals('dimensions:width=200,height=100', (string) $rule);
20 |
21 | $rule = Rule::dimensions()->maxWidth(1000)->maxHeight(500)->ratio(3 / 2);
22 |
23 | $this->assertEquals('dimensions:max_width=1000,max_height=500,ratio=1.5', (string) $rule);
24 |
25 | $rule = new Dimensions(['ratio' => '2/3']);
26 |
27 | $this->assertEquals('dimensions:ratio=2/3', (string) $rule);
28 |
29 | $rule = Rule::dimensions()->minWidth(300)->minHeight(400);
30 |
31 | $this->assertEquals('dimensions:min_width=300,min_height=400', (string) $rule);
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/Contracts/Validation/Factory.php:
--------------------------------------------------------------------------------
1 | assertEquals('in:"Laravel","Framework","PHP"', (string) $rule);
19 |
20 | $rule = new In(['Life, the Universe and Everything', 'this is a "quote"']);
21 |
22 | $this->assertEquals('in:"Life, the Universe and Everything","this is a ""quote"""', (string) $rule);
23 |
24 | $rule = new In(["a,b\nc,d"]);
25 |
26 | $this->assertEquals("in:\"a,b\nc,d\"", (string) $rule);
27 |
28 | $rule = Rule::in([1, 2, 3, 4]);
29 |
30 | $this->assertEquals('in:"1","2","3","4"', (string) $rule);
31 |
32 | $rule = Rule::in(collect([1, 2, 3, 4]));
33 |
34 | $this->assertEquals('in:"1","2","3","4"', (string) $rule);
35 |
36 | $rule = Rule::in(new Values);
37 |
38 | $this->assertEquals('in:"1","2","3","4"', (string) $rule);
39 |
40 | $rule = Rule::in('1', '2', '3', '4');
41 |
42 | $this->assertEquals('in:"1","2","3","4"', (string) $rule);
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/tests/Cases/ValidationAddFailureTest.php:
--------------------------------------------------------------------------------
1 | getIlluminateArrayTranslator();
19 |
20 | return new Validator($trans, ['foo' => ['bar' => ['baz' => '']]], ['foo.bar.baz' => 'sometimes|required']);
21 | }
22 |
23 | public function testAddFailureExists()
24 | {
25 | $validator = $this->makeValidator();
26 | $method_name = 'addFailure';
27 | $this->assertTrue(method_exists($validator, $method_name));
28 | $this->assertTrue(is_callable([$validator, $method_name]));
29 | }
30 |
31 | public function testAddFailureIsFunctional()
32 | {
33 | $attribute = 'Eugene';
34 | $validator = $this->makeValidator();
35 | $validator->addFailure($attribute, 'not_in');
36 | $messages = json_decode($validator->messages());
37 | $this->assertSame($messages->{'foo.bar.baz'}[0], 'validation.required', 'initial data in messages is lost');
38 | $this->assertSame($messages->{$attribute}[0], 'validation.not_in', 'new data in messages was not added');
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/Contracts/Validation/Validator.php:
--------------------------------------------------------------------------------
1 | callback = $callback;
39 | }
40 |
41 | /**
42 | * Determine if the validation rule passes.
43 | *
44 | * @param string $attribute
45 | * @param mixed $value
46 | * @return bool
47 | */
48 | public function passes($attribute, $value)
49 | {
50 | $this->failed = false;
51 |
52 | $this->callback->__invoke($attribute, $value, function ($message) {
53 | $this->failed = true;
54 |
55 | $this->message = $message;
56 | });
57 |
58 | return ! $this->failed;
59 | }
60 |
61 | /**
62 | * Get the validation error message.
63 | *
64 | * @return string
65 | */
66 | public function message()
67 | {
68 | return $this->message;
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "chunhei2008/hyperf-validation",
3 | "type": "library",
4 | "license": "MIT",
5 | "keywords": [
6 | "php",
7 | "hyperf"
8 | ],
9 | "description": "chunhei2008/hyperf-validation",
10 | "autoload": {
11 | "psr-4": {
12 | "Chunhei2008\\Hyperf\\Validation\\": "src/"
13 | }
14 | },
15 | "autoload-dev": {
16 | "psr-4": {
17 | "Chunhei2008\\HyperfTest\\Validation\\": "tests"
18 | }
19 | },
20 | "require": {
21 | "php": ">=7.2",
22 | "ext-swoole": ">=4.3",
23 | "chunhei2008/hyperf-translation": "^1.0",
24 | "egulias/email-validator": "^2.1",
25 | "hyperf/command": "^1.0",
26 | "hyperf/database": "^1.0",
27 | "hyperf/devtool": "^1.0",
28 | "hyperf/di": "1.0.*",
29 | "hyperf/framework": "1.0.*",
30 | "hyperf/http-server": "~1.0.0",
31 | "hyperf/utils": "1.0.*",
32 | "nesbot/carbon": "^2.21",
33 | "psr/container": "^1.0",
34 | "psr/http-message": "^1.0",
35 | "symfony/http-foundation": "^4.3"
36 | },
37 | "require-dev": {
38 | "friendsofphp/php-cs-fixer": "^2.14",
39 | "hyperf/db-connection": "^1.0",
40 | "hyperf/testing": "1.0.*",
41 | "mockery/mockery": "^1.2",
42 | "phpstan/phpstan": "^0.10.5",
43 | "swoft/swoole-ide-helper": "dev-master"
44 | },
45 | "config": {
46 | "sort-packages": true
47 | },
48 | "scripts": {
49 | "test": "co-phpunit -c phpunit.xml --colors=always",
50 | "analyze": "phpstan analyse --memory-limit 300M -l 0 ./src",
51 | "cs-fix": "php-cs-fixer fix $1"
52 | },
53 | "extra": {
54 | "hyperf": {
55 | "config": "Chunhei2008\\Hyperf\\Validation\\ConfigProvider"
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/Rules/Unique.php:
--------------------------------------------------------------------------------
1 | ignoreModel($id, $idColumn);
36 | }
37 |
38 | $this->ignore = $id;
39 | $this->idColumn = $idColumn ?? 'id';
40 |
41 | return $this;
42 | }
43 |
44 | /**
45 | * Ignore the given model during the unique check.
46 | *
47 | * @param \Hyperf\Database\Model\Model $model
48 | * @param string|null $idColumn
49 | * @return $this
50 | */
51 | public function ignoreModel($model, $idColumn = null)
52 | {
53 | $this->idColumn = $idColumn ?? $model->getKeyName();
54 | $this->ignore = $model->{$this->idColumn};
55 |
56 | return $this;
57 | }
58 |
59 | /**
60 | * Convert the rule to a validation string.
61 | *
62 | * @return string
63 | */
64 | public function __toString()
65 | {
66 | return rtrim(sprintf('unique:%s,%s,%s,%s,%s',
67 | $this->table,
68 | $this->column,
69 | $this->ignore ? '"'.addslashes($this->ignore).'"' : 'NULL',
70 | $this->idColumn,
71 | $this->formatWheres()
72 | ), ',');
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/ValidatesWhenResolvedTrait.php:
--------------------------------------------------------------------------------
1 | prepareForValidation();
18 |
19 | if (! $this->passesAuthorization()) {
20 | $this->failedAuthorization();
21 | }
22 |
23 | $instance = $this->getValidatorInstance();
24 |
25 | if ($instance->fails()) {
26 | $this->failedValidation($instance);
27 | }
28 | }
29 |
30 | /**
31 | * Prepare the data for validation.
32 | *
33 | * @return void
34 | */
35 | protected function prepareForValidation()
36 | {
37 | // no default action
38 | }
39 |
40 | /**
41 | * Get the validator instance for the request.
42 | *
43 | * @return \Chunhei2008\Hyperf\Validation\Contracts\Validation\Validator
44 | */
45 | protected function getValidatorInstance()
46 | {
47 | return $this->validator();
48 | }
49 |
50 | /**
51 | * Handle a failed validation attempt.
52 | *
53 | * @param \Chunhei2008\Hyperf\Validation\Contracts\Validation\Validator $validator
54 | * @return void
55 | *
56 | * @throws \Chunhei2008\Hyperf\Validation\ValidationException
57 | */
58 | protected function failedValidation(Validator $validator)
59 | {
60 | throw new ValidationException($validator);
61 | }
62 |
63 | /**
64 | * Determine if the request passes the authorization check.
65 | *
66 | * @return bool
67 | */
68 | protected function passesAuthorization()
69 | {
70 | if (method_exists($this, 'authorize')) {
71 | return $this->authorize();
72 | }
73 |
74 | return true;
75 | }
76 |
77 | /**
78 | * Handle a failed authorization attempt.
79 | *
80 | * @return void
81 | *
82 | * @throws \Chunhei2008\Hyperf\Validation\UnauthorizedException
83 | */
84 | protected function failedAuthorization()
85 | {
86 | throw new UnauthorizedException;
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/tests/Cases/ValidationUniqueRuleTest.php:
--------------------------------------------------------------------------------
1 | where('foo', 'bar');
15 | $this->assertEquals('unique:table,NULL,NULL,id,foo,"bar"', (string) $rule);
16 |
17 | $rule = new Unique('table', 'column');
18 | $rule->ignore('Taylor, Otwell', 'id_column');
19 | $rule->where('foo', 'bar');
20 | $this->assertEquals('unique:table,column,"Taylor, Otwell",id_column,foo,"bar"', (string) $rule);
21 |
22 | $rule = new Unique('table', 'column');
23 | $rule->ignore('Taylor, Otwell"\'..-"', 'id_column');
24 | $rule->where('foo', 'bar');
25 | $this->assertEquals('unique:table,column,"Taylor, Otwell\"\\\'..-\"",id_column,foo,"bar"', (string) $rule);
26 | $this->assertEquals('Taylor, Otwell"\'..-"', stripslashes(str_getcsv('table,column,"Taylor, Otwell\"\\\'..-\"",id_column,foo,"bar"')[2]));
27 | $this->assertEquals('id_column', stripslashes(str_getcsv('table,column,"Taylor, Otwell\"\\\'..-\"",id_column,foo,"bar"')[3]));
28 |
29 | $rule = new Unique('table', 'column');
30 | $rule->ignore(null, 'id_column');
31 | $rule->where('foo', 'bar');
32 | $this->assertEquals('unique:table,column,NULL,id_column,foo,"bar"', (string) $rule);
33 |
34 | $model = new EloquentModelStub(['id_column' => 1]);
35 |
36 | $rule = new Unique('table', 'column');
37 | $rule->ignore($model);
38 | $rule->where('foo', 'bar');
39 | $this->assertEquals('unique:table,column,"1",id_column,foo,"bar"', (string) $rule);
40 |
41 | $rule = new Unique('table', 'column');
42 | $rule->ignore($model, 'id_column');
43 | $rule->where('foo', 'bar');
44 | $this->assertEquals('unique:table,column,"1",id_column,foo,"bar"', (string) $rule);
45 |
46 | $rule = new Unique('table');
47 | $rule->where('foo', '"bar"');
48 | $this->assertEquals('unique:table,NULL,NULL,id,foo,"""bar"""', (string) $rule);
49 | }
50 | }
51 |
52 | class EloquentModelStub extends Model
53 | {
54 | protected $primaryKey = 'id_column';
55 | protected $guarded = [];
56 | }
57 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Hyperf Validation
2 |
3 |
4 | ## About
5 |
6 | chunhei2008/hyperf-validation 是对Laravel Validation的移植(不包含门面部分),具体使用方法可以参考Laravel Validation 的使用。已不再维护,推荐使用Hyperf官方组件
7 |
8 |
9 | ## Install
10 |
11 | ```
12 | composer require chunhei2008/hyperf-validation
13 |
14 | ```
15 |
16 | ## Config
17 |
18 |
19 | ### publish config
20 | ```
21 | php bin/hyperf.php vendor:publish chunhei2008/hyperf-translation
22 |
23 | ```
24 |
25 | ### config path
26 |
27 | ```
28 | your/config/path/autoload/translation.php
29 |
30 | ```
31 |
32 | ### config content
33 |
34 | ```
35 | 'en',
50 | 'fallback_locale' => '',
51 | 'lang' => BASE_PATH . '/resources/lang',
52 | ];
53 |
54 | ```
55 |
56 | ### exception handler
57 |
58 | ```
59 | [
74 | 'http' => [
75 | \Chunhei2008\Hyperf\Validation\ValidationExceptionHandler::class,
76 | ],
77 | ],
78 | ];
79 |
80 | ```
81 |
82 | ### validation middleware
83 |
84 | ```
85 | [
100 | \Chunhei2008\Hyperf\Validation\Middleware\ValidationMiddleware::class,
101 | ],
102 | ];
103 |
104 | ```
105 |
106 |
107 | ## Usage
108 |
109 |
110 | ### gen request
111 |
112 | ```
113 | php bin/hyperf.php gen:request FooRequest
114 | ```
115 |
116 |
117 | ```
118 | class IndexController extends Controller
119 | {
120 |
121 |
122 | public function foo(FooRequest $request)
123 | {
124 | // todo
125 | }
126 | }
127 |
128 |
129 | ```
--------------------------------------------------------------------------------
/src/Rule.php:
--------------------------------------------------------------------------------
1 | toArray();
45 | }
46 |
47 | return new Rules\In(is_array($values) ? $values : func_get_args());
48 | }
49 |
50 | /**
51 | * Get a not_in constraint builder instance.
52 | *
53 | * @param Arrayable|array|string $values
54 | * @return \Chunhei2008\Hyperf\Validation\Rules\NotIn
55 | */
56 | public static function notIn($values)
57 | {
58 | if ($values instanceof Arrayable) {
59 | $values = $values->toArray();
60 | }
61 |
62 | return new Rules\NotIn(is_array($values) ? $values : func_get_args());
63 | }
64 |
65 | /**
66 | * Get a required_if constraint builder instance.
67 | *
68 | * @param callable|bool $callback
69 | * @return \Chunhei2008\Hyperf\Validation\Rules\RequiredIf
70 | */
71 | public static function requiredIf($callback)
72 | {
73 | return new Rules\RequiredIf($callback);
74 | }
75 |
76 | /**
77 | * Get a unique constraint builder instance.
78 | *
79 | * @param string $table
80 | * @param string $column
81 | * @return \Chunhei2008\Hyperf\Validation\Rules\Unique
82 | */
83 | public static function unique($table, $column = 'NULL')
84 | {
85 | return new Rules\Unique($table, $column);
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/.php_cs:
--------------------------------------------------------------------------------
1 | setRiskyAllowed(true)
14 | ->setRules([
15 | '@PSR2' => true,
16 | '@Symfony' => true,
17 | '@DoctrineAnnotation' => true,
18 | '@PhpCsFixer' => true,
19 | 'header_comment' => [
20 | 'commentType' => 'PHPDoc',
21 | 'header' => $header,
22 | 'separate' => 'none',
23 | 'location' => 'after_declare_strict',
24 | ],
25 | 'array_syntax' => [
26 | 'syntax' => 'short'
27 | ],
28 | 'list_syntax' => [
29 | 'syntax' => 'short'
30 | ],
31 | 'concat_space' => [
32 | 'spacing' => 'one'
33 | ],
34 | 'blank_line_before_statement' => [
35 | 'statements' => [
36 | 'declare',
37 | ],
38 | ],
39 | 'general_phpdoc_annotation_remove' => [
40 | 'annotations' => [
41 | 'author'
42 | ],
43 | ],
44 | 'ordered_imports' => [
45 | 'imports_order' => [
46 | 'class', 'function', 'const',
47 | ],
48 | 'sort_algorithm' => 'alpha',
49 | ],
50 | 'single_line_comment_style' => [
51 | 'comment_types' => [
52 | ],
53 | ],
54 | 'list_syntax' => [
55 | 'syntax' => 'short',
56 | ],
57 | 'yoda_style' => [
58 | 'always_move_variable' => false,
59 | 'equal' => false,
60 | 'identical' => false,
61 | ],
62 | 'phpdoc_align' => [
63 | 'align' => 'left',
64 | ],
65 | 'multiline_whitespace_before_semicolons' => [
66 | 'strategy' => 'no_multi_line',
67 | ],
68 | 'class_attributes_separation' => true,
69 | 'combine_consecutive_unsets' => true,
70 | 'declare_strict_types' => true,
71 | 'linebreak_after_opening_tag' => true,
72 | 'lowercase_constants' => true,
73 | 'lowercase_static_reference' => true,
74 | 'no_useless_else' => true,
75 | 'no_unused_imports' => true,
76 | 'not_operator_with_successor_space' => true,
77 | 'not_operator_with_space' => false,
78 | 'ordered_class_elements' => true,
79 | 'php_unit_strict' => false,
80 | 'phpdoc_separation' => false,
81 | 'single_quote' => true,
82 | 'standardize_not_equals' => true,
83 | 'multiline_comment_opening_closing' => true,
84 | ])
85 | ->setFinder(
86 | PhpCsFixer\Finder::create()
87 | ->exclude('vendor')
88 | ->in(__DIR__)
89 | )
90 | ->setUsingCache(false);
91 |
--------------------------------------------------------------------------------
/src/Contracts/Support/MessageBag.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * Date: 2019/7/26 03:37
8 | * Copyright: (C) 2014, Guangzhou YIDEJIA Network Technology Co., Ltd.
9 | */
10 |
11 | namespace Chunhei2008\Hyperf\Validation\Contracts\Support;
12 |
13 |
14 | use Hyperf\Utils\Contracts\Arrayable;
15 |
16 | interface MessageBag extends Arrayable
17 | {
18 | /**
19 | * Get the keys present in the message bag.
20 | *
21 | * @return array
22 | */
23 | public function keys();
24 |
25 | /**
26 | * Add a message to the bag.
27 | *
28 | * @param string $key
29 | * @param string $message
30 | * @return $this
31 | */
32 | public function add($key, $message);
33 |
34 | /**
35 | * Merge a new array of messages into the bag.
36 | *
37 | * @param MessageProvider|array $messages
38 | * @return $this
39 | */
40 | public function merge($messages);
41 |
42 | /**
43 | * Determine if messages exist for a given key.
44 | *
45 | * @param string|array $key
46 | * @return bool
47 | */
48 | public function has($key);
49 |
50 | /**
51 | * Get the first message from the bag for a given key.
52 | *
53 | * @param string|null $key
54 | * @param string|null $format
55 | * @return string
56 | */
57 | public function first($key = null, $format = null);
58 |
59 | /**
60 | * Get all of the messages from the bag for a given key.
61 | *
62 | * @param string $key
63 | * @param string|null $format
64 | * @return array
65 | */
66 | public function get($key, $format = null);
67 |
68 | /**
69 | * Get all of the messages for every key in the bag.
70 | *
71 | * @param string|null $format
72 | * @return array
73 | */
74 | public function all($format = null);
75 |
76 | /**
77 | * Get the raw messages in the container.
78 | *
79 | * @return array
80 | */
81 | public function getMessages();
82 |
83 | /**
84 | * Get the default message format.
85 | *
86 | * @return string
87 | */
88 | public function getFormat();
89 |
90 | /**
91 | * Set the default message format.
92 | *
93 | * @param string $format
94 | * @return $this
95 | */
96 | public function setFormat($format = ':message');
97 |
98 | /**
99 | * Determine if the message bag has any messages.
100 | *
101 | * @return bool
102 | */
103 | public function isEmpty();
104 |
105 | /**
106 | * Determine if the message bag has any messages.
107 | *
108 | * @return bool
109 | */
110 | public function isNotEmpty();
111 |
112 | /**
113 | * Get the number of messages in the container.
114 | *
115 | * @return int
116 | */
117 | public function count();
118 | }
--------------------------------------------------------------------------------
/src/Rules/Dimensions.php:
--------------------------------------------------------------------------------
1 | constraints = $constraints;
23 | }
24 |
25 | /**
26 | * Set the "width" constraint.
27 | *
28 | * @param int $value
29 | * @return $this
30 | */
31 | public function width($value)
32 | {
33 | $this->constraints['width'] = $value;
34 |
35 | return $this;
36 | }
37 |
38 | /**
39 | * Set the "height" constraint.
40 | *
41 | * @param int $value
42 | * @return $this
43 | */
44 | public function height($value)
45 | {
46 | $this->constraints['height'] = $value;
47 |
48 | return $this;
49 | }
50 |
51 | /**
52 | * Set the "min width" constraint.
53 | *
54 | * @param int $value
55 | * @return $this
56 | */
57 | public function minWidth($value)
58 | {
59 | $this->constraints['min_width'] = $value;
60 |
61 | return $this;
62 | }
63 |
64 | /**
65 | * Set the "min height" constraint.
66 | *
67 | * @param int $value
68 | * @return $this
69 | */
70 | public function minHeight($value)
71 | {
72 | $this->constraints['min_height'] = $value;
73 |
74 | return $this;
75 | }
76 |
77 | /**
78 | * Set the "max width" constraint.
79 | *
80 | * @param int $value
81 | * @return $this
82 | */
83 | public function maxWidth($value)
84 | {
85 | $this->constraints['max_width'] = $value;
86 |
87 | return $this;
88 | }
89 |
90 | /**
91 | * Set the "max height" constraint.
92 | *
93 | * @param int $value
94 | * @return $this
95 | */
96 | public function maxHeight($value)
97 | {
98 | $this->constraints['max_height'] = $value;
99 |
100 | return $this;
101 | }
102 |
103 | /**
104 | * Set the "ratio" constraint.
105 | *
106 | * @param float $value
107 | * @return $this
108 | */
109 | public function ratio($value)
110 | {
111 | $this->constraints['ratio'] = $value;
112 |
113 | return $this;
114 | }
115 |
116 | /**
117 | * Convert the rule to a validation string.
118 | *
119 | * @return string
120 | */
121 | public function __toString()
122 | {
123 | $result = '';
124 |
125 | foreach ($this->constraints as $key => $value) {
126 | $result .= "$key=$value,";
127 | }
128 |
129 | return 'dimensions:'.substr($result, 0, -1);
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/tests/Cases/ValidationDatabasePresenceVerifierTest.php:
--------------------------------------------------------------------------------
1 | setConnection('connection');
23 | $db->shouldReceive('connection')->once()->with('connection')->andReturn($conn = m::mock(stdClass::class));
24 | $conn->shouldReceive('table')->once()->with('table')->andReturn($builder = m::mock(stdClass::class));
25 | $builder->shouldReceive('useWritePdo')->once()->andReturn($builder);
26 | $builder->shouldReceive('where')->with('column', '=', 'value')->andReturn($builder);
27 | $extra = ['foo' => 'NULL', 'bar' => 'NOT_NULL', 'baz' => 'taylor', 'faz' => true, 'not' => '!admin'];
28 | $builder->shouldReceive('whereNull')->with('foo');
29 | $builder->shouldReceive('whereNotNull')->with('bar');
30 | $builder->shouldReceive('where')->with('baz', 'taylor');
31 | $builder->shouldReceive('where')->with('faz', true);
32 | $builder->shouldReceive('where')->with('not', '!=', 'admin');
33 | $builder->shouldReceive('count')->once()->andReturn(100);
34 |
35 | $this->assertEquals(100, $verifier->getCount('table', 'column', 'value', null, null, $extra));
36 | }
37 |
38 | public function testBasicCountWithClosures()
39 | {
40 | $verifier = new DatabasePresenceVerifier($db = m::mock(ConnectionResolverInterface::class));
41 | $verifier->setConnection('connection');
42 | $db->shouldReceive('connection')->once()->with('connection')->andReturn($conn = m::mock(stdClass::class));
43 | $conn->shouldReceive('table')->once()->with('table')->andReturn($builder = m::mock(stdClass::class));
44 | $builder->shouldReceive('useWritePdo')->once()->andReturn($builder);
45 | $builder->shouldReceive('where')->with('column', '=', 'value')->andReturn($builder);
46 | $closure = function ($query) {
47 | $query->where('closure', 1);
48 | };
49 | $extra = ['foo' => 'NULL', 'bar' => 'NOT_NULL', 'baz' => 'taylor', 'faz' => true, 'not' => '!admin', 0 => $closure];
50 | $builder->shouldReceive('whereNull')->with('foo');
51 | $builder->shouldReceive('whereNotNull')->with('bar');
52 | $builder->shouldReceive('where')->with('baz', 'taylor');
53 | $builder->shouldReceive('where')->with('faz', true);
54 | $builder->shouldReceive('where')->with('not', '!=', 'admin');
55 | $builder->shouldReceive('where')->with(m::type(Closure::class))->andReturnUsing(function () use ($builder, $closure) {
56 | $closure($builder);
57 | });
58 | $builder->shouldReceive('where')->with('closure', 1);
59 | $builder->shouldReceive('count')->once()->andReturn(100);
60 |
61 | $this->assertEquals(100, $verifier->getCount('table', 'column', 'value', null, null, $extra));
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/ValidationData.php:
--------------------------------------------------------------------------------
1 | $value) {
61 | if ((bool) preg_match('/^'.$pattern.'/', $key, $matches)) {
62 | $keys[] = $matches[0];
63 | }
64 | }
65 |
66 | $keys = array_unique($keys);
67 |
68 | $data = [];
69 |
70 | foreach ($keys as $key) {
71 | $data[$key] = Arr::get($masterData, $key);
72 | }
73 |
74 | return $data;
75 | }
76 |
77 | /**
78 | * Extract data based on the given dot-notated path.
79 | *
80 | * Used to extract a sub-section of the data for faster iteration.
81 | *
82 | * @param string $attribute
83 | * @param array $masterData
84 | * @return array
85 | */
86 | public static function extractDataFromPath($attribute, $masterData)
87 | {
88 | $results = [];
89 |
90 | $value = Arr::get($masterData, $attribute, '__missing__');
91 |
92 | if ($value !== '__missing__') {
93 | Arr::set($results, $attribute, $value);
94 | }
95 |
96 | return $results;
97 | }
98 |
99 | /**
100 | * Get the explicit part of the attribute name.
101 | *
102 | * E.g. 'foo.bar.*.baz' -> 'foo.bar'
103 | *
104 | * Allows us to not spin through all of the flattened data for some operations.
105 | *
106 | * @param string $attribute
107 | * @return string
108 | */
109 | public static function getLeadingExplicitAttributePath($attribute)
110 | {
111 | return rtrim(explode('*', $attribute)[0], '.') ?: null;
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/src/ValidationException.php:
--------------------------------------------------------------------------------
1 | response = $response;
58 | $this->errorBag = $errorBag;
59 | $this->validator = $validator;
60 | }
61 |
62 | /**
63 | * Create a new validation exception from a plain array of messages.
64 | *
65 | * @param array $messages
66 | * @return static
67 | */
68 | public static function withMessages(array $messages)
69 | {
70 | return new static(tap(Factory::make([], []), function ($validator) use ($messages) {
71 | foreach ($messages as $key => $value) {
72 | foreach (Arr::wrap($value) as $message) {
73 | $validator->errors()->add($key, $message);
74 | }
75 | }
76 | }));
77 | }
78 |
79 | /**
80 | * Get all of the validation error messages.
81 | *
82 | * @return array
83 | */
84 | public function errors()
85 | {
86 | return $this->validator->errors()->messages();
87 | }
88 |
89 | /**
90 | * Set the HTTP status code to be used for the response.
91 | *
92 | * @param int $status
93 | * @return $this
94 | */
95 | public function status($status)
96 | {
97 | $this->status = $status;
98 |
99 | return $this;
100 | }
101 |
102 | /**
103 | * Set the error bag on the exception.
104 | *
105 | * @param string $errorBag
106 | * @return $this
107 | */
108 | public function errorBag($errorBag)
109 | {
110 | $this->errorBag = $errorBag;
111 |
112 | return $this;
113 | }
114 |
115 | /**
116 | * Set the URL to redirect to on a validation error.
117 | *
118 | * @param string $url
119 | * @return $this
120 | */
121 | public function redirectTo($url)
122 | {
123 | $this->redirectTo = $url;
124 |
125 | return $this;
126 | }
127 |
128 | /**
129 | * Get the underlying response instance.
130 | *
131 | * @return \Symfony\Component\HttpFoundation\Response|null
132 | */
133 | public function getResponse()
134 | {
135 | return $this->response;
136 | }
137 | }
138 |
--------------------------------------------------------------------------------
/src/DatabasePresenceVerifier.php:
--------------------------------------------------------------------------------
1 | db = $db;
35 | }
36 |
37 | /**
38 | * Count the number of objects in a collection having the given value.
39 | *
40 | * @param string $collection
41 | * @param string $column
42 | * @param string $value
43 | * @param int|null $excludeId
44 | * @param string|null $idColumn
45 | * @param array $extra
46 | * @return int
47 | */
48 | public function getCount($collection, $column, $value, $excludeId = null, $idColumn = null, array $extra = [])
49 | {
50 | $query = $this->table($collection)->where($column, '=', $value);
51 |
52 | if (! is_null($excludeId) && $excludeId !== 'NULL') {
53 | $query->where($idColumn ?: 'id', '<>', $excludeId);
54 | }
55 |
56 | return $this->addConditions($query, $extra)->count();
57 | }
58 |
59 | /**
60 | * Count the number of objects in a collection with the given values.
61 | *
62 | * @param string $collection
63 | * @param string $column
64 | * @param array $values
65 | * @param array $extra
66 | * @return int
67 | */
68 | public function getMultiCount($collection, $column, array $values, array $extra = [])
69 | {
70 | $query = $this->table($collection)->whereIn($column, $values);
71 |
72 | return $this->addConditions($query, $extra)->distinct()->count($column);
73 | }
74 |
75 | /**
76 | * Add the given conditions to the query.
77 | *
78 | * @param \Hyperf\Database\Query\Builder $query
79 | * @param array $conditions
80 | * @return \Hyperf\Database\Query\Builder
81 | */
82 | protected function addConditions($query, $conditions)
83 | {
84 | foreach ($conditions as $key => $value) {
85 | if ($value instanceof Closure) {
86 | $query->where(function ($query) use ($value) {
87 | $value($query);
88 | });
89 | } else {
90 | $this->addWhere($query, $key, $value);
91 | }
92 | }
93 |
94 | return $query;
95 | }
96 |
97 | /**
98 | * Add a "where" clause to the given query.
99 | *
100 | * @param \Hyperf\Database\Query\Builder $query
101 | * @param string $key
102 | * @param string $extraValue
103 | * @return void
104 | */
105 | protected function addWhere($query, $key, $extraValue)
106 | {
107 | if ($extraValue === 'NULL') {
108 | $query->whereNull($key);
109 | } elseif ($extraValue === 'NOT_NULL') {
110 | $query->whereNotNull($key);
111 | } elseif (Str::startsWith($extraValue, '!')) {
112 | $query->where($key, '!=', mb_substr($extraValue, 1));
113 | } else {
114 | $query->where($key, $extraValue);
115 | }
116 | }
117 |
118 | /**
119 | * Get a query builder for the given table.
120 | *
121 | * @param string $table
122 | * @return \Hyperf\Database\Query\Builder
123 | */
124 | public function table($table)
125 | {
126 | return $this->db->connection($this->connection)->table($table)->useWritePdo();
127 | }
128 |
129 | /**
130 | * Set the connection to be used.
131 | *
132 | * @param string $connection
133 | * @return void
134 | */
135 | public function setConnection($connection)
136 | {
137 | $this->connection = $connection;
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/src/Rules/DatabaseRule.php:
--------------------------------------------------------------------------------
1 | table = $table;
47 | $this->column = $column;
48 | }
49 |
50 | /**
51 | * Set a "where" constraint on the query.
52 | *
53 | * @param string|\Closure $column
54 | * @param array|string|null $value
55 | * @return $this
56 | */
57 | public function where($column, $value = null)
58 | {
59 | if (is_array($value)) {
60 | return $this->whereIn($column, $value);
61 | }
62 |
63 | if ($column instanceof Closure) {
64 | return $this->using($column);
65 | }
66 |
67 | $this->wheres[] = compact('column', 'value');
68 |
69 | return $this;
70 | }
71 |
72 | /**
73 | * Set a "where not" constraint on the query.
74 | *
75 | * @param string $column
76 | * @param array|string $value
77 | * @return $this
78 | */
79 | public function whereNot($column, $value)
80 | {
81 | if (is_array($value)) {
82 | return $this->whereNotIn($column, $value);
83 | }
84 |
85 | return $this->where($column, '!'.$value);
86 | }
87 |
88 | /**
89 | * Set a "where null" constraint on the query.
90 | *
91 | * @param string $column
92 | * @return $this
93 | */
94 | public function whereNull($column)
95 | {
96 | return $this->where($column, 'NULL');
97 | }
98 |
99 | /**
100 | * Set a "where not null" constraint on the query.
101 | *
102 | * @param string $column
103 | * @return $this
104 | */
105 | public function whereNotNull($column)
106 | {
107 | return $this->where($column, 'NOT_NULL');
108 | }
109 |
110 | /**
111 | * Set a "where in" constraint on the query.
112 | *
113 | * @param string $column
114 | * @param array $values
115 | * @return $this
116 | */
117 | public function whereIn($column, array $values)
118 | {
119 | return $this->where(function ($query) use ($column, $values) {
120 | $query->whereIn($column, $values);
121 | });
122 | }
123 |
124 | /**
125 | * Set a "where not in" constraint on the query.
126 | *
127 | * @param string $column
128 | * @param array $values
129 | * @return $this
130 | */
131 | public function whereNotIn($column, array $values)
132 | {
133 | return $this->where(function ($query) use ($column, $values) {
134 | $query->whereNotIn($column, $values);
135 | });
136 | }
137 |
138 | /**
139 | * Register a custom query callback.
140 | *
141 | * @param \Closure $callback
142 | * @return $this
143 | */
144 | public function using(Closure $callback)
145 | {
146 | $this->using[] = $callback;
147 |
148 | return $this;
149 | }
150 |
151 | /**
152 | * Get the custom query callbacks for the rule.
153 | *
154 | * @return array
155 | */
156 | public function queryCallbacks()
157 | {
158 | return $this->using;
159 | }
160 |
161 | /**
162 | * Format the where clauses.
163 | *
164 | * @return string
165 | */
166 | protected function formatWheres()
167 | {
168 | return collect($this->wheres)->map(function ($where) {
169 | return $where['column'].','.'"'.str_replace('"', '""', $where['value']).'"';
170 | })->implode(',');
171 | }
172 | }
173 |
--------------------------------------------------------------------------------
/tests/Cases/ValidationFactoryTest.php:
--------------------------------------------------------------------------------
1 | make(['foo' => 'bar'], ['baz' => 'boom']);
25 | $this->assertEquals($translator, $validator->getTranslator());
26 | $this->assertEquals(['foo' => 'bar'], $validator->getData());
27 | $this->assertEquals(['baz' => ['boom']], $validator->getRules());
28 |
29 | $presence = m::mock(PresenceVerifierInterface::class);
30 | $noop1 = function () {
31 | //
32 | };
33 | $noop2 = function () {
34 | //
35 | };
36 | $noop3 = function () {
37 | //
38 | };
39 | $factory->extend('foo', $noop1);
40 | $factory->extendImplicit('implicit', $noop2);
41 | $factory->extendDependent('dependent', $noop3);
42 | $factory->replacer('replacer', $noop3);
43 | $factory->setPresenceVerifier($presence);
44 | $validator = $factory->make([], []);
45 | $this->assertEquals(['foo' => $noop1, 'implicit' => $noop2, 'dependent' => $noop3], $validator->extensions);
46 | $this->assertEquals(['replacer' => $noop3], $validator->replacers);
47 | $this->assertEquals($presence, $validator->getPresenceVerifier());
48 |
49 | $presence = m::mock(PresenceVerifierInterface::class);
50 | $factory->extend('foo', $noop1, 'foo!');
51 | $factory->extendImplicit('implicit', $noop2, 'implicit!');
52 | $factory->extendImplicit('dependent', $noop3, 'dependent!');
53 | $factory->setPresenceVerifier($presence);
54 | $validator = $factory->make([], []);
55 | $this->assertEquals(['foo' => $noop1, 'implicit' => $noop2, 'dependent' => $noop3], $validator->extensions);
56 | $this->assertEquals(['foo' => 'foo!', 'implicit' => 'implicit!', 'dependent' => 'dependent!'], $validator->fallbackMessages);
57 | $this->assertEquals($presence, $validator->getPresenceVerifier());
58 | }
59 |
60 | public function testValidateCallsValidateOnTheValidator()
61 | {
62 | $validator = m::mock(Validator::class);
63 | $translator = m::mock(TranslatorInterface::class);
64 | $factory = m::mock(Factory::class.'[make]', [$translator]);
65 |
66 | $factory->shouldReceive('make')->once()
67 | ->with(['foo' => 'bar', 'baz' => 'boom'], ['foo' => 'required'], [], [])
68 | ->andReturn($validator);
69 |
70 | $validator->shouldReceive('validate')->once()->andReturn(['foo' => 'bar']);
71 |
72 | $validated = $factory->validate(
73 | ['foo' => 'bar', 'baz' => 'boom'],
74 | ['foo' => 'required']
75 | );
76 |
77 | $this->assertEquals($validated, ['foo' => 'bar']);
78 | }
79 |
80 | public function testCustomResolverIsCalled()
81 | {
82 | unset($_SERVER['__validator.factory']);
83 | $translator = m::mock(TranslatorInterface::class);
84 | $factory = new Factory($translator);
85 | $factory->resolver(function ($translator, $data, $rules) {
86 | $_SERVER['__validator.factory'] = true;
87 |
88 | return new Validator($translator, $data, $rules);
89 | });
90 | $validator = $factory->make(['foo' => 'bar'], ['baz' => 'boom']);
91 |
92 | $this->assertTrue($_SERVER['__validator.factory']);
93 | $this->assertEquals($translator, $validator->getTranslator());
94 | $this->assertEquals(['foo' => 'bar'], $validator->getData());
95 | $this->assertEquals(['baz' => ['boom']], $validator->getRules());
96 | unset($_SERVER['__validator.factory']);
97 | }
98 |
99 | public function testValidateMethodCanBeCalledPublicly()
100 | {
101 | $translator = m::mock(TranslatorInterface::class);
102 | $factory = new Factory($translator);
103 | $factory->extend('foo', function ($attribute, $value, $parameters, $validator) {
104 | return $validator->validateArray($attribute, $value);
105 | });
106 |
107 | $validator = $factory->make(['bar' => ['baz']], ['bar' => 'foo']);
108 | $this->assertTrue($validator->passes());
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/src/Middleware/ValidationMiddleware.php:
--------------------------------------------------------------------------------
1 | container = $container;
44 | $factory = $this->container->get(DispatcherFactory::class);
45 | $this->dispatcher = $factory->getDispatcher('http');
46 | }
47 |
48 | /**
49 | * Process an incoming server request and return a response, optionally delegating
50 | * response creation to a handler.
51 | */
52 | public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
53 | {
54 | /** @var ResponseInterface $response */
55 | $uri = $request->getUri();
56 | /**
57 | * @var array
58 | * Returns array with one of the following formats:
59 | * [self::NOT_FOUND]
60 | * [self::METHOD_NOT_ALLOWED, ['GET', 'OTHER_ALLOWED_METHODS']]
61 | * [self::FOUND, $handler, ['varName' => 'value', ...]]
62 | */
63 | $routes = $this->dispatcher->dispatch($request->getMethod(), $uri->getPath());
64 | switch ($routes[0]) {
65 | case Dispatcher::NOT_FOUND:
66 | case Dispatcher::METHOD_NOT_ALLOWED:
67 | break;
68 | case Dispatcher::FOUND:
69 | $this->handleFound($routes, $request);
70 | break;
71 | }
72 |
73 | return $handler->handle($request);
74 | }
75 |
76 | /**
77 | * @param array|string $handler
78 | */
79 | protected function prepareHandler($handler): array
80 | {
81 | if (is_string($handler)) {
82 | if (strpos($handler, '@') !== false) {
83 | return explode('@', $handler);
84 | }
85 |
86 | return explode('::', $handler);
87 | }
88 | if (is_array($handler) && isset($handler[0], $handler[1])) {
89 | return $handler;
90 | }
91 | throw new \RuntimeException('Handler not exist.');
92 | }
93 |
94 | /**
95 | * Handle the response when found.
96 | *
97 | * @return array|Arrayable|mixed|ResponseInterface|string
98 | */
99 | protected function handleFound(array $routes, ServerRequestInterface $request)
100 | {
101 | if ($routes[1] instanceof Closure) {
102 | // Do nothing
103 | } else {
104 | [$controller, $action] = $this->prepareHandler($routes[1]);
105 | if (! method_exists($controller, $action)) {
106 | // Route found, but the handler does not exist.
107 | return $this->response()->withStatus(500)->withBody(new SwooleStream('Method of class does not exist.'));
108 | }
109 | $params = $this->parseParameters($controller, $action, $routes[2]);
110 | foreach ($params as $param) {
111 | if ($param instanceof ValidatesWhenResolved) {
112 | $param->validateResolved();
113 | }
114 | }
115 | }
116 | }
117 |
118 | /**
119 | * Get response instance from context.
120 | */
121 | protected function response(): ResponseInterface
122 | {
123 | return Context::get(ResponseInterface::class);
124 | }
125 |
126 | /**
127 | * Parse the parameters of method definitions, and then bind the specified arguments or
128 | * get the value from DI container, combine to a argument array that should be injected
129 | * and return the array.
130 | */
131 | protected function parseParameters(string $controller, string $action, array $arguments): array
132 | {
133 | $injections = [];
134 | $this->container->get(MethodDefinitionCollectorInterface::class);
135 |
136 | $definitions = $this->container->get(MethodDefinitionCollectorInterface::class)->getParameters($controller, $action);
137 | foreach ($definitions ?? [] as $pos => $definition) {
138 | $value = $arguments[$pos] ?? $arguments[$definition->getMeta('name')] ?? null;
139 | if ($value === null) {
140 | if ($definition->getMeta('defaultValueAvailable')) {
141 | $injections[] = $definition->getMeta('defaultValue');
142 | } elseif ($definition->allowsNull()) {
143 | $injections[] = null;
144 | } elseif ($this->container->has($definition->getName())) {
145 | $injections[] = $this->container->get($definition->getName());
146 | } else {
147 | throw new \InvalidArgumentException("Parameter '{$definition->getMeta('name')}' "
148 | . "of {$controller}::{$action} should not be null");
149 | }
150 | } else {
151 | $injections[] = $this->container->get(NormalizerInterface::class)->denormalize($value, $definition->getName());
152 | }
153 | }
154 |
155 | return $injections;
156 | }
157 | }
--------------------------------------------------------------------------------
/src/Request/FormRequest.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * Date: 2019/7/26 02:04
8 | * Copyright: (C) 2014, Guangzhou YIDEJIA Network Technology Co., Ltd.
9 | */
10 |
11 | namespace Chunhei2008\Hyperf\Validation\Request;
12 |
13 | use Chunhei2008\Hyperf\Validation\Contracts\Validation\Factory;
14 | use Hyperf\HttpServer\Request;
15 | //use Illuminate\Http\JsonResponse;
16 | //use Illuminate\Routing\Redirector;
17 | use Symfony\Component\HttpFoundation\JsonResponse;
18 | use Chunhei2008\Hyperf\Validation\Contracts\Validation\Validator;
19 | use Chunhei2008\Hyperf\Validation\ValidationException;
20 | //use Illuminate\Auth\Access\AuthorizationException;
21 | use Chunhei2008\Hyperf\Validation\Contracts\Validation\Factory as ValidationFactory;
22 | use Chunhei2008\Hyperf\Validation\Contracts\Validation\ValidatesWhenResolved;
23 | use Chunhei2008\Hyperf\Validation\ValidatesWhenResolvedTrait;
24 | use Psr\Container\ContainerInterface;
25 |
26 |
27 | class FormRequest extends Request implements ValidatesWhenResolved
28 | {
29 | use ValidatesWhenResolvedTrait;
30 |
31 | /**
32 | * The container instance.
33 | *
34 | * @var ContainerInterface
35 | */
36 | protected $container;
37 |
38 | // /**
39 | // * The redirector instance.
40 | // *
41 | // * @var \Illuminate\Routing\Redirector
42 | // */
43 | // protected $redirector;
44 | //
45 | // /**
46 | // * The URI to redirect to if validation fails.
47 | // *
48 | // * @var string
49 | // */
50 | // protected $redirect;
51 | //
52 | // /**
53 | // * The route to redirect to if validation fails.
54 | // *
55 | // * @var string
56 | // */
57 | // protected $redirectRoute;
58 | //
59 | // /**
60 | // * The controller action to redirect to if validation fails.
61 | // *
62 | // * @var string
63 | // */
64 | // protected $redirectAction;
65 |
66 | /**
67 | * The key to be used for the view error bag.
68 | *
69 | * @var string
70 | */
71 | protected $errorBag = 'default';
72 |
73 | /**
74 | * The input keys that should not be flashed on redirect.
75 | *
76 | * @var array
77 | */
78 | protected $dontFlash = ['password', 'password_confirmation'];
79 |
80 |
81 | public function __construct(ContainerInterface $container)
82 | {
83 | $this->setContainer($container);
84 | }
85 |
86 | /**
87 | * Get the validator instance for the request.
88 | *
89 | * @return \Chunhei2008\Hyperf\Validation\Contracts\Validation\Validator
90 | */
91 | protected function getValidatorInstance()
92 | {
93 | $factory = $this->container->get(ValidationFactory::class);
94 |
95 | if (method_exists($this, 'validator')) {
96 | $validator = call_user_func_array([$this, 'validator'], compact('factory'));
97 | } else {
98 | $validator = $this->createDefaultValidator($factory);
99 | }
100 |
101 | if (method_exists($this, 'withValidator')) {
102 | $this->withValidator($validator);
103 | }
104 |
105 | return $validator;
106 | }
107 |
108 | /**
109 | * Create the default validator instance.
110 | *
111 | * @param Factory $factory
112 | * @return \Chunhei2008\Hyperf\Validation\Contracts\Validation\Validator
113 | */
114 | protected function createDefaultValidator(ValidationFactory $factory)
115 | {
116 | return $factory->make(
117 | $this->validationData(), call_user_func_array([$this, 'rules'], []),
118 | $this->messages(), $this->attributes()
119 | );
120 | }
121 |
122 | /**
123 | * Get data to be validated from the request.
124 | *
125 | * @return array
126 | */
127 | protected function validationData()
128 | {
129 | return $this->all();
130 | }
131 |
132 | /**
133 | * Handle a failed validation attempt.
134 | *
135 | * @param Validator $validator
136 | * @return void
137 | *
138 | * @throws ValidationException
139 | */
140 | protected function failedValidation(Validator $validator)
141 | {
142 | throw new ValidationException($validator, $this->response(
143 | $this->formatErrors($validator)
144 | ));
145 | }
146 |
147 | /**
148 | * Get the proper failed validation response for the request.
149 | *
150 | * @param array $errors
151 | * @return \Symfony\Component\HttpFoundation\Response
152 | */
153 | public function response(array $errors)
154 | {
155 | // if ($this->expectsJson()) {
156 | // return new JsonResponse($errors, 422);
157 | // }
158 |
159 | // return $this->redirector->to($this->getRedirectUrl())
160 | // ->withInput($this->except($this->dontFlash))
161 | // ->withErrors($errors, $this->errorBag);
162 | return new JsonResponse($errors, 422);
163 | }
164 |
165 | /**
166 | * Format the errors from the given Validator instance.
167 | *
168 | * @param Validator $validator
169 | * @return array
170 | */
171 | protected function formatErrors(Validator $validator)
172 | {
173 | return $validator->getMessageBag()->toArray();
174 | }
175 |
176 | // /**
177 | // * Get the URL to redirect to on a validation error.
178 | // *
179 | // * @return string
180 | // */
181 | // protected function getRedirectUrl()
182 | // {
183 | // $url = $this->redirector->getUrlGenerator();
184 | //
185 | // if ($this->redirect) {
186 | // return $url->to($this->redirect);
187 | // } elseif ($this->redirectRoute) {
188 | // return $url->route($this->redirectRoute);
189 | // } elseif ($this->redirectAction) {
190 | // return $url->action($this->redirectAction);
191 | // }
192 | //
193 | // return $url->previous();
194 | // }
195 |
196 | /**
197 | * Determine if the request passes the authorization check.
198 | *
199 | * @return bool
200 | */
201 | protected function passesAuthorization()
202 | {
203 | if (method_exists($this, 'authorize')) {
204 | return call_user_func_array([$this, 'authorize'], []);
205 | }
206 |
207 | return false;
208 | }
209 |
210 | /**
211 | * Handle a failed authorization attempt.
212 | *
213 | * @return void
214 | *
215 | * @throws \Illuminate\Auth\Access\AuthorizationException
216 | */
217 | protected function failedAuthorization()
218 | {
219 | // throw new AuthorizationException('This action is unauthorized.');
220 | }
221 |
222 | /**
223 | * Get custom messages for validator errors.
224 | *
225 | * @return array
226 | */
227 | public function messages()
228 | {
229 | return [];
230 | }
231 |
232 | /**
233 | * Get custom attributes for validator errors.
234 | *
235 | * @return array
236 | */
237 | public function attributes()
238 | {
239 | return [];
240 | }
241 |
242 | // /**
243 | // * Set the Redirector instance.
244 | // *
245 | // * @param \Illuminate\Routing\Redirector $redirector
246 | // * @return $this
247 | // */
248 | // public function setRedirector(Redirector $redirector)
249 | // {
250 | // $this->redirector = $redirector;
251 | //
252 | // return $this;
253 | // }
254 |
255 | /**
256 | * Set the container implementation.
257 | *
258 | * @param ContainerInterface $container
259 | * @return $this
260 | */
261 | public function setContainer(ContainerInterface $container)
262 | {
263 | $this->container = $container;
264 |
265 | return $this;
266 | }
267 | }
--------------------------------------------------------------------------------
/tests/Cases/ValidationExistsRuleTest.php:
--------------------------------------------------------------------------------
1 | shouldReceive('has')->andReturn(true);
38 | $container->shouldReceive('get')->with('db.connector.mysql')->andReturn(new MySqlConnector());
39 | $connector = new ConnectionFactory($container);
40 | $dbConfig = [
41 | 'driver' => env('DB_DRIVER', 'mysql'),
42 | 'host' => env('DB_HOST', 'localhost'),
43 | 'database' => env('DB_DATABASE', 'hyperf'),
44 | 'username' => env('DB_USERNAME', 'root'),
45 | 'password' => env('DB_PASSWORD', ''),
46 | 'charset' => env('DB_CHARSET', 'utf8'),
47 | 'collation' => env('DB_COLLATION', 'utf8_unicode_ci'),
48 | 'prefix' => env('DB_PREFIX', ''),
49 | ];
50 | $connection = $connector->make($dbConfig);
51 | $resolver = new ConnectionResolver(['default' => $connection]);
52 | $container->shouldReceive('get')->with(DBConnectionResolver::class)->andReturn($resolver);
53 | ApplicationContext::setContainer($container);
54 | Register::setConnectionResolver($resolver);
55 | $container->shouldReceive('get')->with(EventDispatcherInterface::class)->andReturn(new EventDispatcher());
56 |
57 | $this->createSchema();
58 | }
59 |
60 | public function testItCorrectlyFormatsAStringVersionOfTheRule()
61 | {
62 | $rule = new Exists('table');
63 | $rule->where('foo', 'bar');
64 | $this->assertEquals('exists:table,NULL,foo,"bar"', (string)$rule);
65 |
66 | $rule = new Exists('table', 'column');
67 | $rule->where('foo', 'bar');
68 | $this->assertEquals('exists:table,column,foo,"bar"', (string)$rule);
69 | }
70 |
71 | public function testItChoosesValidRecordsUsingWhereInRule()
72 | {
73 | $rule = new Exists('users', 'id');
74 | $rule->whereIn('type', ['foo', 'bar']);
75 |
76 | EloquentTestUser::create(['id' => '1', 'type' => 'foo']);
77 | EloquentTestUser::create(['id' => '2', 'type' => 'bar']);
78 | EloquentTestUser::create(['id' => '3', 'type' => 'baz']);
79 | EloquentTestUser::create(['id' => '4', 'type' => 'other']);
80 |
81 | $trans = $this->getIlluminateArrayTranslator();
82 | $v = new Validator($trans, [], ['id' => $rule]);
83 | $v->setPresenceVerifier(new DatabasePresenceVerifier(Register::getConnectionResolver()));
84 |
85 | $v->setData(['id' => 1]);
86 | $this->assertTrue($v->passes());
87 | $v->setData(['id' => 2]);
88 | $this->assertTrue($v->passes());
89 | $v->setData(['id' => 3]);
90 | $this->assertFalse($v->passes());
91 | $v->setData(['id' => 4]);
92 | $this->assertFalse($v->passes());
93 | }
94 |
95 | public function testItChoosesValidRecordsUsingWhereNotInRule()
96 | {
97 | $rule = new Exists('users', 'id');
98 | $rule->whereNotIn('type', ['foo', 'bar']);
99 |
100 | EloquentTestUser::create(['id' => '1', 'type' => 'foo']);
101 | EloquentTestUser::create(['id' => '2', 'type' => 'bar']);
102 | EloquentTestUser::create(['id' => '3', 'type' => 'baz']);
103 | EloquentTestUser::create(['id' => '4', 'type' => 'other']);
104 |
105 | $trans = $this->getIlluminateArrayTranslator();
106 | $v = new Validator($trans, [], ['id' => $rule]);
107 | $v->setPresenceVerifier(new DatabasePresenceVerifier(Register::getConnectionResolver()));
108 |
109 | $v->setData(['id' => 1]);
110 | $this->assertFalse($v->passes());
111 | $v->setData(['id' => 2]);
112 | $this->assertFalse($v->passes());
113 | $v->setData(['id' => 3]);
114 | $this->assertTrue($v->passes());
115 | $v->setData(['id' => 4]);
116 | $this->assertTrue($v->passes());
117 | }
118 |
119 | public function testItChoosesValidRecordsUsingWhereNotInAndWhereNotInRulesTogether()
120 | {
121 | $rule = new Exists('users', 'id');
122 | $rule->whereIn('type', ['foo', 'bar', 'baz'])->whereNotIn('type', ['foo', 'bar']);
123 |
124 | EloquentTestUser::create(['id' => '1', 'type' => 'foo']);
125 | EloquentTestUser::create(['id' => '2', 'type' => 'bar']);
126 | EloquentTestUser::create(['id' => '3', 'type' => 'baz']);
127 | EloquentTestUser::create(['id' => '4', 'type' => 'other']);
128 |
129 | $trans = $this->getIlluminateArrayTranslator();
130 | $v = new Validator($trans, [], ['id' => $rule]);
131 | $v->setPresenceVerifier(new DatabasePresenceVerifier(Register::getConnectionResolver()));
132 |
133 | $v->setData(['id' => 1]);
134 | $this->assertFalse($v->passes());
135 | $v->setData(['id' => 2]);
136 | $this->assertFalse($v->passes());
137 | $v->setData(['id' => 3]);
138 | $this->assertTrue($v->passes());
139 | $v->setData(['id' => 4]);
140 | $this->assertFalse($v->passes());
141 | }
142 |
143 | protected function createSchema()
144 | {
145 | $this->schema('default')->create('users', function ($table) {
146 | $table->unsignedInteger('id');
147 | $table->string('type');
148 | });
149 | }
150 |
151 | /**
152 | * Get a schema builder instance.
153 | *
154 | * @return Builder
155 | */
156 | protected function schema($connection = 'default')
157 | {
158 | return $this->connection($connection)->getSchemaBuilder();
159 | }
160 |
161 | /**
162 | * Get a database connection instance.
163 | *
164 | * @return Connection
165 | */
166 | protected function connection($connection = 'default')
167 | {
168 | return $this->getConnectionResolver()->connection($connection);
169 | }
170 |
171 | /**
172 | * Get connection resolver.
173 | *
174 | * @return ConnectionResolverInterface
175 | */
176 | protected function getConnectionResolver()
177 | {
178 | return Register::getConnectionResolver();
179 | }
180 |
181 | /**
182 | * Tear down the database schema.
183 | *
184 | * @return void
185 | */
186 | protected function tearDown(): void
187 | {
188 | $this->schema('default')->drop('users');
189 |
190 | m::close();
191 | }
192 |
193 | public function getIlluminateArrayTranslator()
194 | {
195 | return new Translator(
196 | new ArrayLoader, 'en'
197 | );
198 | }
199 | }
200 |
201 | /**
202 | * Eloquent Models.
203 | */
204 | class EloquentTestUser extends Eloquent
205 | {
206 | protected $table = 'users';
207 | protected $guarded = [];
208 | public $timestamps = false;
209 | }
210 |
--------------------------------------------------------------------------------
/src/ValidationRuleParser.php:
--------------------------------------------------------------------------------
1 | data = $data;
37 | }
38 |
39 | /**
40 | * Parse the human-friendly rules into a full rules array for the validator.
41 | *
42 | * @param array $rules
43 | * @return \stdClass
44 | */
45 | public function explode($rules)
46 | {
47 | $this->implicitAttributes = [];
48 |
49 | $rules = $this->explodeRules($rules);
50 |
51 | return (object)[
52 | 'rules' => $rules,
53 | 'implicitAttributes' => $this->implicitAttributes,
54 | ];
55 | }
56 |
57 | /**
58 | * Explode the rules into an array of explicit rules.
59 | *
60 | * @param array $rules
61 | * @return array
62 | */
63 | protected function explodeRules($rules)
64 | {
65 | foreach ($rules as $key => $rule) {
66 | if (Str::contains((string)$key, '*')) {
67 | $rules = $this->explodeWildcardRules($rules, $key, [$rule]);
68 |
69 | unset($rules[$key]);
70 | } else {
71 | $rules[$key] = $this->explodeExplicitRule($rule);
72 | }
73 | }
74 |
75 | return $rules;
76 | }
77 |
78 | /**
79 | * Explode the explicit rule into an array if necessary.
80 | *
81 | * @param mixed $rule
82 | * @return array
83 | */
84 | protected function explodeExplicitRule($rule)
85 | {
86 | if (is_string($rule)) {
87 | return explode('|', $rule);
88 | } elseif (is_object($rule)) {
89 | return [$this->prepareRule($rule)];
90 | }
91 |
92 | return array_map([$this, 'prepareRule'], $rule);
93 | }
94 |
95 | /**
96 | * Prepare the given rule for the Validator.
97 | *
98 | * @param mixed $rule
99 | * @return mixed
100 | */
101 | protected function prepareRule($rule)
102 | {
103 | if ($rule instanceof Closure) {
104 | $rule = new ClosureValidationRule($rule);
105 | }
106 |
107 | if (!is_object($rule) ||
108 | $rule instanceof RuleContract ||
109 | ($rule instanceof Exists && $rule->queryCallbacks()) ||
110 | ($rule instanceof Unique && $rule->queryCallbacks())) {
111 | return $rule;
112 | }
113 |
114 | return (string)$rule;
115 | }
116 |
117 | /**
118 | * Define a set of rules that apply to each element in an array attribute.
119 | *
120 | * @param array $results
121 | * @param string $attribute
122 | * @param string|array $rules
123 | * @return array
124 | */
125 | protected function explodeWildcardRules($results, $attribute, $rules)
126 | {
127 | $pattern = str_replace('\*', '[^\.]*', preg_quote($attribute));
128 |
129 | $data = ValidationData::initializeAndGatherData($attribute, $this->data);
130 |
131 | foreach ($data as $key => $value) {
132 | if (Str::startsWith($key, $attribute) || (bool)preg_match('/^' . $pattern . '\z/', $key)) {
133 | foreach ((array)$rules as $rule) {
134 | $this->implicitAttributes[$attribute][] = $key;
135 |
136 | $results = $this->mergeRules($results, $key, $rule);
137 | }
138 | }
139 | }
140 |
141 | return $results;
142 | }
143 |
144 | /**
145 | * Merge additional rules into a given attribute(s).
146 | *
147 | * @param array $results
148 | * @param string|array $attribute
149 | * @param string|array $rules
150 | * @return array
151 | */
152 | public function mergeRules($results, $attribute, $rules = [])
153 | {
154 | if (is_array($attribute)) {
155 | foreach ((array)$attribute as $innerAttribute => $innerRules) {
156 | $results = $this->mergeRulesForAttribute($results, $innerAttribute, $innerRules);
157 | }
158 |
159 | return $results;
160 | }
161 |
162 | return $this->mergeRulesForAttribute(
163 | $results, $attribute, $rules
164 | );
165 | }
166 |
167 | /**
168 | * Merge additional rules into a given attribute.
169 | *
170 | * @param array $results
171 | * @param string $attribute
172 | * @param string|array $rules
173 | * @return array
174 | */
175 | protected function mergeRulesForAttribute($results, $attribute, $rules)
176 | {
177 | $merge = head($this->explodeRules([$rules]));
178 |
179 | $results[$attribute] = array_merge(
180 | isset($results[$attribute]) ? $this->explodeExplicitRule($results[$attribute]) : [], $merge
181 | );
182 |
183 | return $results;
184 | }
185 |
186 | /**
187 | * Extract the rule name and parameters from a rule.
188 | *
189 | * @param array|string $rules
190 | * @return array
191 | */
192 | public static function parse($rules)
193 | {
194 | if ($rules instanceof RuleContract) {
195 | return [$rules, []];
196 | }
197 |
198 | if (is_array($rules)) {
199 | $rules = static::parseArrayRule($rules);
200 | } else {
201 | $rules = static::parseStringRule($rules);
202 | }
203 |
204 | $rules[0] = static::normalizeRule($rules[0]);
205 |
206 | return $rules;
207 | }
208 |
209 | /**
210 | * Parse an array based rule.
211 | *
212 | * @param array $rules
213 | * @return array
214 | */
215 | protected static function parseArrayRule(array $rules)
216 | {
217 | return [Str::studly(trim(Arr::get($rules, (string)0))), array_slice($rules, 1)];
218 | }
219 |
220 | /**
221 | * Parse a string based rule.
222 | *
223 | * @param string $rules
224 | * @return array
225 | */
226 | protected static function parseStringRule($rules)
227 | {
228 | $parameters = [];
229 |
230 | // The format for specifying validation rules and parameters follows an
231 | // easy {rule}:{parameters} formatting convention. For instance the
232 | // rule "Max:3" states that the value may only be three letters.
233 | if (strpos($rules, ':') !== false) {
234 | [$rules, $parameter] = explode(':', $rules, 2);
235 |
236 | $parameters = static::parseParameters($rules, $parameter);
237 | }
238 |
239 | return [Str::studly(trim($rules)), $parameters];
240 | }
241 |
242 | /**
243 | * Parse a parameter list.
244 | *
245 | * @param string $rule
246 | * @param string $parameter
247 | * @return array
248 | */
249 | protected static function parseParameters($rule, $parameter)
250 | {
251 | $rule = strtolower($rule);
252 |
253 | if (in_array($rule, ['regex', 'not_regex', 'notregex'], true)) {
254 | return [$parameter];
255 | }
256 |
257 | return str_getcsv($parameter);
258 | }
259 |
260 | /**
261 | * Normalizes a rule so that we can accept short types.
262 | *
263 | * @param string $rule
264 | * @return string
265 | */
266 | protected static function normalizeRule($rule)
267 | {
268 | switch ($rule) {
269 | case 'Int':
270 | return 'Integer';
271 | case 'Bool':
272 | return 'Boolean';
273 | default:
274 | return $rule;
275 | }
276 | }
277 | }
278 |
--------------------------------------------------------------------------------
/src/Factory.php:
--------------------------------------------------------------------------------
1 | container = $container;
86 | $this->translator = $translator;
87 | }
88 |
89 | /**
90 | * Create a new Validator instance.
91 | *
92 | * @param array $data
93 | * @param array $rules
94 | * @param array $messages
95 | * @param array $customAttributes
96 | * @return \Chunhei2008\Hyperf\Validation\Validator
97 | */
98 | public function make(array $data, array $rules, array $messages = [], array $customAttributes = [])
99 | {
100 | $validator = $this->resolve(
101 | $data, $rules, $messages, $customAttributes
102 | );
103 |
104 | // The presence verifier is responsible for checking the unique and exists data
105 | // for the validator. It is behind an interface so that multiple versions of
106 | // it may be written besides database. We'll inject it into the validator.
107 | if (! is_null($this->verifier)) {
108 | $validator->setPresenceVerifier($this->verifier);
109 | }
110 |
111 | // Next we'll set the IoC container instance of the validator, which is used to
112 | // resolve out class based validator extensions. If it is not set then these
113 | // types of extensions will not be possible on these validation instances.
114 | if (! is_null($this->container)) {
115 | $validator->setContainer($this->container);
116 | }
117 |
118 | $this->addExtensions($validator);
119 |
120 | return $validator;
121 | }
122 |
123 | /**
124 | * Validate the given data against the provided rules.
125 | *
126 | * @param array $data
127 | * @param array $rules
128 | * @param array $messages
129 | * @param array $customAttributes
130 | * @return array
131 | *
132 | * @throws \Chunhei2008\Hyperf\Validation\ValidationException
133 | */
134 | public function validate(array $data, array $rules, array $messages = [], array $customAttributes = [])
135 | {
136 | return $this->make($data, $rules, $messages, $customAttributes)->validate();
137 | }
138 |
139 | /**
140 | * Resolve a new Validator instance.
141 | *
142 | * @param array $data
143 | * @param array $rules
144 | * @param array $messages
145 | * @param array $customAttributes
146 | * @return \Chunhei2008\Hyperf\Validation\Validator
147 | */
148 | protected function resolve(array $data, array $rules, array $messages, array $customAttributes)
149 | {
150 | if (is_null($this->resolver)) {
151 | return new Validator($this->translator, $data, $rules, $messages, $customAttributes);
152 | }
153 |
154 | return call_user_func($this->resolver, $this->translator, $data, $rules, $messages, $customAttributes);
155 | }
156 |
157 | /**
158 | * Add the extensions to a validator instance.
159 | *
160 | * @param \Chunhei2008\Hyperf\Validation\Validator $validator
161 | * @return void
162 | */
163 | protected function addExtensions(Validator $validator)
164 | {
165 | $validator->addExtensions($this->extensions);
166 |
167 | // Next, we will add the implicit extensions, which are similar to the required
168 | // and accepted rule in that they are run even if the attributes is not in a
169 | // array of data that is given to a validator instances via instantiation.
170 | $validator->addImplicitExtensions($this->implicitExtensions);
171 |
172 | $validator->addDependentExtensions($this->dependentExtensions);
173 |
174 | $validator->addReplacers($this->replacers);
175 |
176 | $validator->setFallbackMessages($this->fallbackMessages);
177 | }
178 |
179 | /**
180 | * Register a custom validator extension.
181 | *
182 | * @param string $rule
183 | * @param \Closure|string $extension
184 | * @param string|null $message
185 | * @return void
186 | */
187 | public function extend($rule, $extension, $message = null)
188 | {
189 | $this->extensions[$rule] = $extension;
190 |
191 | if ($message) {
192 | $this->fallbackMessages[Str::snake($rule)] = $message;
193 | }
194 | }
195 |
196 | /**
197 | * Register a custom implicit validator extension.
198 | *
199 | * @param string $rule
200 | * @param \Closure|string $extension
201 | * @param string|null $message
202 | * @return void
203 | */
204 | public function extendImplicit($rule, $extension, $message = null)
205 | {
206 | $this->implicitExtensions[$rule] = $extension;
207 |
208 | if ($message) {
209 | $this->fallbackMessages[Str::snake($rule)] = $message;
210 | }
211 | }
212 |
213 | /**
214 | * Register a custom dependent validator extension.
215 | *
216 | * @param string $rule
217 | * @param \Closure|string $extension
218 | * @param string|null $message
219 | * @return void
220 | */
221 | public function extendDependent($rule, $extension, $message = null)
222 | {
223 | $this->dependentExtensions[$rule] = $extension;
224 |
225 | if ($message) {
226 | $this->fallbackMessages[Str::snake($rule)] = $message;
227 | }
228 | }
229 |
230 | /**
231 | * Register a custom validator message replacer.
232 | *
233 | * @param string $rule
234 | * @param \Closure|string $replacer
235 | * @return void
236 | */
237 | public function replacer($rule, $replacer)
238 | {
239 | $this->replacers[$rule] = $replacer;
240 | }
241 |
242 | /**
243 | * Set the Validator instance resolver.
244 | *
245 | * @param \Closure $resolver
246 | * @return void
247 | */
248 | public function resolver(Closure $resolver)
249 | {
250 | $this->resolver = $resolver;
251 | }
252 |
253 | /**
254 | * Get the Translator implementation.
255 | *
256 | * @return \Chunhei2008\Hyperf\Translation\Contracts\Translator
257 | */
258 | public function getTranslator()
259 | {
260 | return $this->translator;
261 | }
262 |
263 | /**
264 | * Get the Presence Verifier implementation.
265 | *
266 | * @return \Chunhei2008\Hyperf\Validation\PresenceVerifierInterface
267 | */
268 | public function getPresenceVerifier()
269 | {
270 | return $this->verifier;
271 | }
272 |
273 | /**
274 | * Set the Presence Verifier implementation.
275 | *
276 | * @param \Chunhei2008\Hyperf\Validation\PresenceVerifierInterface $presenceVerifier
277 | * @return void
278 | */
279 | public function setPresenceVerifier(PresenceVerifierInterface $presenceVerifier)
280 | {
281 | $this->verifier = $presenceVerifier;
282 | }
283 | }
284 |
--------------------------------------------------------------------------------
/src/Support/MessageBag.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * Date: 2019-07-25 18:35
8 | * Copyright: (C) 2014, Guangzhou YIDEJIA Network Technology Co., Ltd.
9 | */
10 |
11 | namespace Chunhei2008\Hyperf\Validation\Support;
12 |
13 | use Countable;
14 | use Hyperf\Utils\Arr;
15 | use Hyperf\Utils\Str;
16 | use JsonSerializable;
17 | use Hyperf\Utils\Contracts\Jsonable;
18 | use Hyperf\Utils\Contracts\Arrayable;
19 | use Chunhei2008\Hyperf\Validation\Contracts\Support\MessageProvider;
20 | use Chunhei2008\Hyperf\Validation\Contracts\Support\MessageBag as MessageBagContract;
21 |
22 | class MessageBag implements Arrayable, Countable, Jsonable, JsonSerializable, MessageBagContract, MessageProvider
23 | {
24 | /**
25 | * All of the registered messages.
26 | *
27 | * @var array
28 | */
29 | protected $messages = [];
30 |
31 | /**
32 | * Default format for message output.
33 | *
34 | * @var string
35 | */
36 | protected $format = ':message';
37 |
38 | /**
39 | * Create a new message bag instance.
40 | *
41 | * @param array $messages
42 | * @return void
43 | */
44 | public function __construct(array $messages = [])
45 | {
46 | foreach ($messages as $key => $value) {
47 | $value = $value instanceof Arrayable ? $value->toArray() : (array)$value;
48 |
49 | $this->messages[$key] = array_unique($value);
50 | }
51 | }
52 |
53 | /**
54 | * Get the keys present in the message bag.
55 | *
56 | * @return array
57 | */
58 | public function keys()
59 | {
60 | return array_keys($this->messages);
61 | }
62 |
63 | /**
64 | * Add a message to the message bag.
65 | *
66 | * @param string $key
67 | * @param string $message
68 | * @return $this
69 | */
70 | public function add($key, $message)
71 | {
72 | if ($this->isUnique($key, $message)) {
73 | $this->messages[$key][] = $message;
74 | }
75 |
76 | return $this;
77 | }
78 |
79 | /**
80 | * Determine if a key and message combination already exists.
81 | *
82 | * @param string $key
83 | * @param string $message
84 | * @return bool
85 | */
86 | protected function isUnique($key, $message)
87 | {
88 | $messages = (array)$this->messages;
89 |
90 | return !isset($messages[$key]) || !in_array($message, $messages[$key]);
91 | }
92 |
93 | /**
94 | * Merge a new array of messages into the message bag.
95 | *
96 | * @param MessageProvider|array $messages
97 | * @return $this
98 | */
99 | public function merge($messages)
100 | {
101 | if ($messages instanceof MessageProvider) {
102 | $messages = $messages->getMessageBag()->getMessages();
103 | }
104 |
105 | $this->messages = array_merge_recursive($this->messages, $messages);
106 |
107 | return $this;
108 | }
109 |
110 | /**
111 | * Determine if messages exist for all of the given keys.
112 | *
113 | * @param array|string $key
114 | * @return bool
115 | */
116 | public function has($key)
117 | {
118 | if ($this->isEmpty()) {
119 | return false;
120 | }
121 |
122 | if (is_null($key)) {
123 | return $this->any();
124 | }
125 |
126 | $keys = is_array($key) ? $key : func_get_args();
127 |
128 | foreach ($keys as $key) {
129 | if ($this->first($key) === '') {
130 | return false;
131 | }
132 | }
133 |
134 | return true;
135 | }
136 |
137 | /**
138 | * Determine if messages exist for any of the given keys.
139 | *
140 | * @param array|string $keys
141 | * @return bool
142 | */
143 | public function hasAny($keys = [])
144 | {
145 | if ($this->isEmpty()) {
146 | return false;
147 | }
148 |
149 | $keys = is_array($keys) ? $keys : func_get_args();
150 |
151 | foreach ($keys as $key) {
152 | if ($this->has($key)) {
153 | return true;
154 | }
155 | }
156 |
157 | return false;
158 | }
159 |
160 | /**
161 | * Get the first message from the message bag for a given key.
162 | *
163 | * @param string $key
164 | * @param string $format
165 | * @return string
166 | */
167 | public function first($key = null, $format = null)
168 | {
169 | $messages = is_null($key) ? $this->all($format) : $this->get($key, $format);
170 |
171 | $firstMessage = Arr::first($messages, null, '');
172 |
173 | return is_array($firstMessage) ? Arr::first($firstMessage) : $firstMessage;
174 | }
175 |
176 | /**
177 | * Get all of the messages from the message bag for a given key.
178 | *
179 | * @param string $key
180 | * @param string $format
181 | * @return array
182 | */
183 | public function get($key, $format = null)
184 | {
185 | // If the message exists in the message bag, we will transform it and return
186 | // the message. Otherwise, we will check if the key is implicit & collect
187 | // all the messages that match the given key and output it as an array.
188 | if (array_key_exists($key, $this->messages)) {
189 | return $this->transform(
190 | $this->messages[$key], $this->checkFormat($format), $key
191 | );
192 | }
193 |
194 | if (Str::contains($key, '*')) {
195 | return $this->getMessagesForWildcardKey($key, $format);
196 | }
197 |
198 | return [];
199 | }
200 |
201 | /**
202 | * Get the messages for a wildcard key.
203 | *
204 | * @param string $key
205 | * @param string|null $format
206 | * @return array
207 | */
208 | protected function getMessagesForWildcardKey($key, $format)
209 | {
210 | return collect($this->messages)
211 | ->filter(function ($messages, $messageKey) use ($key) {
212 | return Str::is($key, $messageKey);
213 | })
214 | ->map(function ($messages, $messageKey) use ($format) {
215 | return $this->transform(
216 | $messages, $this->checkFormat($format), $messageKey
217 | );
218 | })->all();
219 | }
220 |
221 | /**
222 | * Get all of the messages for every key in the message bag.
223 | *
224 | * @param string $format
225 | * @return array
226 | */
227 | public function all($format = null)
228 | {
229 | $format = $this->checkFormat($format);
230 |
231 | $all = [];
232 |
233 | foreach ($this->messages as $key => $messages) {
234 | $all = array_merge($all, $this->transform($messages, $format, $key));
235 | }
236 |
237 | return $all;
238 | }
239 |
240 | /**
241 | * Get all of the unique messages for every key in the message bag.
242 | *
243 | * @param string $format
244 | * @return array
245 | */
246 | public function unique($format = null)
247 | {
248 | return array_unique($this->all($format));
249 | }
250 |
251 | /**
252 | * Format an array of messages.
253 | *
254 | * @param array $messages
255 | * @param string $format
256 | * @param string $messageKey
257 | * @return array
258 | */
259 | protected function transform($messages, $format, $messageKey)
260 | {
261 | return collect((array)$messages)
262 | ->map(function ($message) use ($format, $messageKey) {
263 | // We will simply spin through the given messages and transform each one
264 | // replacing the :message place holder with the real message allowing
265 | // the messages to be easily formatted to each developer's desires.
266 | return str_replace([':message', ':key'], [$message, $messageKey], $format);
267 | })->all();
268 | }
269 |
270 | /**
271 | * Get the appropriate format based on the given format.
272 | *
273 | * @param string $format
274 | * @return string
275 | */
276 | protected function checkFormat($format)
277 | {
278 | return $format ?: $this->format;
279 | }
280 |
281 | /**
282 | * Get the raw messages in the message bag.
283 | *
284 | * @return array
285 | */
286 | public function messages()
287 | {
288 | return $this->messages;
289 | }
290 |
291 | /**
292 | * Get the raw messages in the message bag.
293 | *
294 | * @return array
295 | */
296 | public function getMessages()
297 | {
298 | return $this->messages();
299 | }
300 |
301 | /**
302 | * Get the messages for the instance.
303 | *
304 | * @return MessageBag
305 | */
306 | public function getMessageBag()
307 | {
308 | return $this;
309 | }
310 |
311 | /**
312 | * Get the default message format.
313 | *
314 | * @return string
315 | */
316 | public function getFormat()
317 | {
318 | return $this->format;
319 | }
320 |
321 | /**
322 | * Set the default message format.
323 | *
324 | * @param string $format
325 | * @return MessageBag
326 | */
327 | public function setFormat($format = ':message')
328 | {
329 | $this->format = $format;
330 |
331 | return $this;
332 | }
333 |
334 | /**
335 | * Determine if the message bag has any messages.
336 | *
337 | * @return bool
338 | */
339 | public function isEmpty()
340 | {
341 | return !$this->any();
342 | }
343 |
344 | /**
345 | * Determine if the message bag has any messages.
346 | *
347 | * @return bool
348 | */
349 | public function isNotEmpty()
350 | {
351 | return $this->any();
352 | }
353 |
354 | /**
355 | * Determine if the message bag has any messages.
356 | *
357 | * @return bool
358 | */
359 | public function any()
360 | {
361 | return $this->count() > 0;
362 | }
363 |
364 | /**
365 | * Get the number of messages in the message bag.
366 | *
367 | * @return int
368 | */
369 | public function count()
370 | {
371 | return count($this->messages, COUNT_RECURSIVE) - count($this->messages);
372 | }
373 |
374 | /**
375 | * Get the instance as an array.
376 | *
377 | * @return array
378 | */
379 | public function toArray(): array
380 | {
381 | return $this->getMessages();
382 | }
383 |
384 | /**
385 | * Convert the object into something JSON serializable.
386 | *
387 | * @return array
388 | */
389 | public function jsonSerialize()
390 | {
391 | return $this->toArray();
392 | }
393 |
394 | /**
395 | * Convert the object to its JSON representation.
396 | *
397 | * @param int $options
398 | * @return string
399 | */
400 | public function toJson($options = 0)
401 | {
402 | return json_encode($this->jsonSerialize(), $options);
403 | }
404 |
405 | /**
406 | * Convert the message bag to its string representation.
407 | *
408 | * @return string
409 | */
410 | public function __toString(): string
411 | {
412 | return $this->toJson();
413 | }
414 | }
415 |
--------------------------------------------------------------------------------
/src/Concerns/FormatsMessages.php:
--------------------------------------------------------------------------------
1 | getInlineMessage($attribute, $rule);
24 |
25 | // First we will retrieve the custom message for the validation rule if one
26 | // exists. If a custom validation message is being used we'll return the
27 | // custom message, otherwise we'll keep searching for a valid message.
28 | if (!is_null($inlineMessage)) {
29 | return $inlineMessage;
30 | }
31 |
32 | $lowerRule = Str::snake($rule);
33 |
34 | $customMessage = $this->getCustomMessageFromTranslator(
35 | $customKey = "validation.custom.{$attribute}.{$lowerRule}"
36 | );
37 |
38 | // First we check for a custom defined validation message for the attribute
39 | // and rule. This allows the developer to specify specific messages for
40 | // only some attributes and rules that need to get specially formed.
41 | if ($customMessage !== $customKey) {
42 | return $customMessage;
43 | }
44 |
45 | // If the rule being validated is a "size" rule, we will need to gather the
46 | // specific error message for the type of attribute being validated such
47 | // as a number, file or string which all have different message types.
48 | elseif (in_array($rule, $this->sizeRules)) {
49 | return $this->getSizeMessage($attribute, $rule);
50 | }
51 |
52 | // Finally, if no developer specified messages have been set, and no other
53 | // special messages apply for this rule, we will just pull the default
54 | // messages out of the translator service for this validation rule.
55 | $key = "validation.{$lowerRule}";
56 |
57 | if ($key != ($value = $this->translator->trans($key))) {
58 | return $value;
59 | }
60 |
61 | return $this->getFromLocalArray(
62 | $attribute, $lowerRule, $this->fallbackMessages
63 | ) ?: $key;
64 | }
65 |
66 | /**
67 | * Get the proper inline error message for standard and size rules.
68 | *
69 | * @param string $attribute
70 | * @param string $rule
71 | * @return string|null
72 | */
73 | protected function getInlineMessage($attribute, $rule)
74 | {
75 | $inlineEntry = $this->getFromLocalArray($attribute, Str::snake($rule));
76 |
77 | return is_array($inlineEntry) && in_array($rule, $this->sizeRules)
78 | ? $inlineEntry[$this->getAttributeType($attribute)]
79 | : $inlineEntry;
80 | }
81 |
82 | /**
83 | * Get the inline message for a rule if it exists.
84 | *
85 | * @param string $attribute
86 | * @param string $lowerRule
87 | * @param array|null $source
88 | * @return string|null
89 | */
90 | protected function getFromLocalArray($attribute, $lowerRule, $source = null)
91 | {
92 | $source = $source ?: $this->customMessages;
93 |
94 | $keys = ["{$attribute}.{$lowerRule}", $lowerRule];
95 |
96 | // First we will check for a custom message for an attribute specific rule
97 | // message for the fields, then we will check for a general custom line
98 | // that is not attribute specific. If we find either we'll return it.
99 | foreach ($keys as $key) {
100 | foreach (array_keys($source) as $sourceKey) {
101 | if (Str::is($sourceKey, $key)) {
102 | return $source[$sourceKey];
103 | }
104 | }
105 | }
106 | }
107 |
108 | /**
109 | * Get the custom error message from translator.
110 | *
111 | * @param string $key
112 | * @return string
113 | */
114 | protected function getCustomMessageFromTranslator($key)
115 | {
116 | if (($message = $this->translator->trans($key)) !== $key) {
117 | return $message;
118 | }
119 |
120 | // If an exact match was not found for the key, we will collapse all of these
121 | // messages and loop through them and try to find a wildcard match for the
122 | // given key. Otherwise, we will simply return the key's value back out.
123 | $shortKey = preg_replace(
124 | '/^validation\.custom\./', '', $key
125 | );
126 |
127 | return $this->getWildcardCustomMessages(Arr::dot(
128 | (array)$this->translator->trans('validation.custom')
129 | ), $shortKey, $key);
130 | }
131 |
132 | /**
133 | * Check the given messages for a wildcard key.
134 | *
135 | * @param array $messages
136 | * @param string $search
137 | * @param string $default
138 | * @return string
139 | */
140 | protected function getWildcardCustomMessages($messages, $search, $default)
141 | {
142 | foreach ($messages as $key => $message) {
143 | if ($search === $key || (Str::contains((string)$key, ['*']) && Str::is($key, $search))) {
144 | return $message;
145 | }
146 | }
147 |
148 | return $default;
149 | }
150 |
151 | /**
152 | * Get the proper error message for an attribute and size rule.
153 | *
154 | * @param string $attribute
155 | * @param string $rule
156 | * @return string
157 | */
158 | protected function getSizeMessage($attribute, $rule)
159 | {
160 | $lowerRule = Str::snake($rule);
161 |
162 | // There are three different types of size validations. The attribute may be
163 | // either a number, file, or string so we will check a few things to know
164 | // which type of value it is and return the correct line for that type.
165 | $type = $this->getAttributeType($attribute);
166 |
167 | $key = "validation.{$lowerRule}.{$type}";
168 |
169 | return $this->translator->trans($key);
170 | }
171 |
172 | /**
173 | * Get the data type of the given attribute.
174 | *
175 | * @param string $attribute
176 | * @return string
177 | */
178 | protected function getAttributeType($attribute)
179 | {
180 | // We assume that the attributes present in the file array are files so that
181 | // means that if the attribute does not have a numeric rule and the files
182 | // list doesn't have it we'll just consider it a string by elimination.
183 | if ($this->hasRule($attribute, $this->numericRules)) {
184 | return 'numeric';
185 | } elseif ($this->hasRule($attribute, ['Array'])) {
186 | return 'array';
187 | } elseif ($this->getValue($attribute) instanceof UploadedFile) {
188 | return 'file';
189 | }
190 |
191 | return 'string';
192 | }
193 |
194 | /**
195 | * Replace all error message place-holders with actual values.
196 | *
197 | * @param string $message
198 | * @param string $attribute
199 | * @param string $rule
200 | * @param array $parameters
201 | * @return string
202 | */
203 | public function makeReplacements($message, $attribute, $rule, $parameters)
204 | {
205 | $message = $this->replaceAttributePlaceholder(
206 | $message, $this->getDisplayableAttribute($attribute)
207 | );
208 |
209 | $message = $this->replaceInputPlaceholder($message, $attribute);
210 |
211 | if (isset($this->replacers[Str::snake($rule)])) {
212 | return $this->callReplacer($message, $attribute, Str::snake($rule), $parameters, $this);
213 | } elseif (method_exists($this, $replacer = "replace{$rule}")) {
214 | return $this->$replacer($message, $attribute, $rule, $parameters);
215 | }
216 |
217 | return $message;
218 | }
219 |
220 | /**
221 | * Get the displayable name of the attribute.
222 | *
223 | * @param string $attribute
224 | * @return string
225 | */
226 | public function getDisplayableAttribute($attribute)
227 | {
228 | $primaryAttribute = $this->getPrimaryAttribute($attribute);
229 |
230 | $expectedAttributes = $attribute != $primaryAttribute
231 | ? [$attribute, $primaryAttribute] : [$attribute];
232 |
233 | foreach ($expectedAttributes as $name) {
234 | // The developer may dynamically specify the array of custom attributes on this
235 | // validator instance. If the attribute exists in this array it is used over
236 | // the other ways of pulling the attribute name for this given attributes.
237 | if (isset($this->customAttributes[$name])) {
238 | return $this->customAttributes[$name];
239 | }
240 |
241 | // We allow for a developer to specify language lines for any attribute in this
242 | // application, which allows flexibility for displaying a unique displayable
243 | // version of the attribute name instead of the name used in an HTTP POST.
244 | if ($line = $this->getAttributeFromTranslations($name)) {
245 | return $line;
246 | }
247 | }
248 |
249 | // When no language line has been specified for the attribute and it is also
250 | // an implicit attribute we will display the raw attribute's name and not
251 | // modify it with any of these replacements before we display the name.
252 | if (isset($this->implicitAttributes[$primaryAttribute])) {
253 | return $attribute;
254 | }
255 |
256 | return str_replace('_', ' ', Str::snake($attribute));
257 | }
258 |
259 | /**
260 | * Get the given attribute from the attribute translations.
261 | *
262 | * @param string $name
263 | * @return string
264 | */
265 | protected function getAttributeFromTranslations($name)
266 | {
267 | return Arr::get($this->translator->trans('validation.attributes'), $name);
268 | }
269 |
270 | /**
271 | * Replace the :attribute placeholder in the given message.
272 | *
273 | * @param string $message
274 | * @param string $value
275 | * @return string
276 | */
277 | protected function replaceAttributePlaceholder($message, $value)
278 | {
279 | return str_replace(
280 | [':attribute', ':ATTRIBUTE', ':Attribute'],
281 | [$value, Str::upper($value), Str::ucfirst($value)],
282 | $message
283 | );
284 | }
285 |
286 | /**
287 | * Replace the :input placeholder in the given message.
288 | *
289 | * @param string $message
290 | * @param string $attribute
291 | * @return string
292 | */
293 | protected function replaceInputPlaceholder($message, $attribute)
294 | {
295 | $actualValue = $this->getValue($attribute);
296 |
297 | if (is_scalar($actualValue) || is_null($actualValue)) {
298 | $message = str_replace(':input', $actualValue, $message);
299 | }
300 |
301 | return $message;
302 | }
303 |
304 | /**
305 | * Get the displayable name of the value.
306 | *
307 | * @param string $attribute
308 | * @param mixed $value
309 | * @return string
310 | */
311 | public function getDisplayableValue($attribute, $value)
312 | {
313 | if (isset($this->customValues[$attribute][$value])) {
314 | return $this->customValues[$attribute][$value];
315 | }
316 |
317 | $key = "validation.values.{$attribute}.{$value}";
318 |
319 | if (($line = $this->translator->trans($key)) !== $key) {
320 | return $line;
321 | }
322 |
323 | return $value;
324 | }
325 |
326 | /**
327 | * Transform an array of attributes to their displayable form.
328 | *
329 | * @param array $values
330 | * @return array
331 | */
332 | protected function getAttributeList(array $values)
333 | {
334 | $attributes = [];
335 |
336 | // For each attribute in the list we will simply get its displayable form as
337 | // this is convenient when replacing lists of parameters like some of the
338 | // replacement functions do when formatting out the validation message.
339 | foreach ($values as $key => $value) {
340 | $attributes[$key] = $this->getDisplayableAttribute($value);
341 | }
342 |
343 | return $attributes;
344 | }
345 |
346 | /**
347 | * Call a custom validator message replacer.
348 | *
349 | * @param string $message
350 | * @param string $attribute
351 | * @param string $rule
352 | * @param array $parameters
353 | * @param \Chunhei2008\Hyperf\Validation\Validator $validator
354 | * @return string|null
355 | */
356 | protected function callReplacer($message, $attribute, $rule, $parameters, $validator)
357 | {
358 | $callback = $this->replacers[$rule];
359 |
360 | if ($callback instanceof Closure) {
361 | return call_user_func_array($callback, func_get_args());
362 | } elseif (is_string($callback)) {
363 | return $this->callClassBasedReplacer($callback, $message, $attribute, $rule, $parameters, $validator);
364 | }
365 | }
366 |
367 | /**
368 | * Call a class based validator message replacer.
369 | *
370 | * @param string $callback
371 | * @param string $message
372 | * @param string $attribute
373 | * @param string $rule
374 | * @param array $parameters
375 | * @param \Chunhei2008\Hyperf\Validation\Validator $validator
376 | * @return string
377 | */
378 | protected function callClassBasedReplacer($callback, $message, $attribute, $rule, $parameters, $validator)
379 | {
380 | [$class, $method] = Str::parseCallback($callback, 'replace');
381 |
382 | return call_user_func_array([$this->container->make($class), $method], array_slice(func_get_args(), 1));
383 | }
384 | }
385 |
--------------------------------------------------------------------------------
/src/Concerns/ReplacesAttributes.php:
--------------------------------------------------------------------------------
1 | replaceSame($message, $attribute, $rule, $parameters);
49 | }
50 |
51 | /**
52 | * Replace all place-holders for the digits rule.
53 | *
54 | * @param string $message
55 | * @param string $attribute
56 | * @param string $rule
57 | * @param array $parameters
58 | * @return string
59 | */
60 | protected function replaceDigits($message, $attribute, $rule, $parameters)
61 | {
62 | return str_replace(':digits', $parameters[0], $message);
63 | }
64 |
65 | /**
66 | * Replace all place-holders for the digits (between) rule.
67 | *
68 | * @param string $message
69 | * @param string $attribute
70 | * @param string $rule
71 | * @param array $parameters
72 | * @return string
73 | */
74 | protected function replaceDigitsBetween($message, $attribute, $rule, $parameters)
75 | {
76 | return $this->replaceBetween($message, $attribute, $rule, $parameters);
77 | }
78 |
79 | /**
80 | * Replace all place-holders for the min rule.
81 | *
82 | * @param string $message
83 | * @param string $attribute
84 | * @param string $rule
85 | * @param array $parameters
86 | * @return string
87 | */
88 | protected function replaceMin($message, $attribute, $rule, $parameters)
89 | {
90 | return str_replace(':min', $parameters[0], $message);
91 | }
92 |
93 | /**
94 | * Replace all place-holders for the max rule.
95 | *
96 | * @param string $message
97 | * @param string $attribute
98 | * @param string $rule
99 | * @param array $parameters
100 | * @return string
101 | */
102 | protected function replaceMax($message, $attribute, $rule, $parameters)
103 | {
104 | return str_replace(':max', $parameters[0], $message);
105 | }
106 |
107 | /**
108 | * Replace all place-holders for the in rule.
109 | *
110 | * @param string $message
111 | * @param string $attribute
112 | * @param string $rule
113 | * @param array $parameters
114 | * @return string
115 | */
116 | protected function replaceIn($message, $attribute, $rule, $parameters)
117 | {
118 | foreach ($parameters as &$parameter) {
119 | $parameter = $this->getDisplayableValue($attribute, $parameter);
120 | }
121 |
122 | return str_replace(':values', implode(', ', $parameters), $message);
123 | }
124 |
125 | /**
126 | * Replace all place-holders for the not_in rule.
127 | *
128 | * @param string $message
129 | * @param string $attribute
130 | * @param string $rule
131 | * @param array $parameters
132 | * @return string
133 | */
134 | protected function replaceNotIn($message, $attribute, $rule, $parameters)
135 | {
136 | return $this->replaceIn($message, $attribute, $rule, $parameters);
137 | }
138 |
139 | /**
140 | * Replace all place-holders for the in_array rule.
141 | *
142 | * @param string $message
143 | * @param string $attribute
144 | * @param string $rule
145 | * @param array $parameters
146 | * @return string
147 | */
148 | protected function replaceInArray($message, $attribute, $rule, $parameters)
149 | {
150 | return str_replace(':other', $this->getDisplayableAttribute($parameters[0]), $message);
151 | }
152 |
153 | /**
154 | * Replace all place-holders for the mimetypes rule.
155 | *
156 | * @param string $message
157 | * @param string $attribute
158 | * @param string $rule
159 | * @param array $parameters
160 | * @return string
161 | */
162 | protected function replaceMimetypes($message, $attribute, $rule, $parameters)
163 | {
164 | return str_replace(':values', implode(', ', $parameters), $message);
165 | }
166 |
167 | /**
168 | * Replace all place-holders for the mimes rule.
169 | *
170 | * @param string $message
171 | * @param string $attribute
172 | * @param string $rule
173 | * @param array $parameters
174 | * @return string
175 | */
176 | protected function replaceMimes($message, $attribute, $rule, $parameters)
177 | {
178 | return str_replace(':values', implode(', ', $parameters), $message);
179 | }
180 |
181 | /**
182 | * Replace all place-holders for the required_with rule.
183 | *
184 | * @param string $message
185 | * @param string $attribute
186 | * @param string $rule
187 | * @param array $parameters
188 | * @return string
189 | */
190 | protected function replaceRequiredWith($message, $attribute, $rule, $parameters)
191 | {
192 | return str_replace(':values', implode(' / ', $this->getAttributeList($parameters)), $message);
193 | }
194 |
195 | /**
196 | * Replace all place-holders for the required_with_all rule.
197 | *
198 | * @param string $message
199 | * @param string $attribute
200 | * @param string $rule
201 | * @param array $parameters
202 | * @return string
203 | */
204 | protected function replaceRequiredWithAll($message, $attribute, $rule, $parameters)
205 | {
206 | return $this->replaceRequiredWith($message, $attribute, $rule, $parameters);
207 | }
208 |
209 | /**
210 | * Replace all place-holders for the required_without rule.
211 | *
212 | * @param string $message
213 | * @param string $attribute
214 | * @param string $rule
215 | * @param array $parameters
216 | * @return string
217 | */
218 | protected function replaceRequiredWithout($message, $attribute, $rule, $parameters)
219 | {
220 | return $this->replaceRequiredWith($message, $attribute, $rule, $parameters);
221 | }
222 |
223 | /**
224 | * Replace all place-holders for the required_without_all rule.
225 | *
226 | * @param string $message
227 | * @param string $attribute
228 | * @param string $rule
229 | * @param array $parameters
230 | * @return string
231 | */
232 | protected function replaceRequiredWithoutAll($message, $attribute, $rule, $parameters)
233 | {
234 | return $this->replaceRequiredWith($message, $attribute, $rule, $parameters);
235 | }
236 |
237 | /**
238 | * Replace all place-holders for the size rule.
239 | *
240 | * @param string $message
241 | * @param string $attribute
242 | * @param string $rule
243 | * @param array $parameters
244 | * @return string
245 | */
246 | protected function replaceSize($message, $attribute, $rule, $parameters)
247 | {
248 | return str_replace(':size', $parameters[0], $message);
249 | }
250 |
251 | /**
252 | * Replace all place-holders for the gt rule.
253 | *
254 | * @param string $message
255 | * @param string $attribute
256 | * @param string $rule
257 | * @param array $parameters
258 | * @return string
259 | */
260 | protected function replaceGt($message, $attribute, $rule, $parameters)
261 | {
262 | if (is_null($value = $this->getValue($parameters[0]))) {
263 | return str_replace(':value', $parameters[0], $message);
264 | }
265 |
266 | return str_replace(':value', $this->getSize($attribute, $value), $message);
267 | }
268 |
269 | /**
270 | * Replace all place-holders for the lt rule.
271 | *
272 | * @param string $message
273 | * @param string $attribute
274 | * @param string $rule
275 | * @param array $parameters
276 | * @return string
277 | */
278 | protected function replaceLt($message, $attribute, $rule, $parameters)
279 | {
280 | if (is_null($value = $this->getValue($parameters[0]))) {
281 | return str_replace(':value', $parameters[0], $message);
282 | }
283 |
284 | return str_replace(':value', $this->getSize($attribute, $value), $message);
285 | }
286 |
287 | /**
288 | * Replace all place-holders for the gte rule.
289 | *
290 | * @param string $message
291 | * @param string $attribute
292 | * @param string $rule
293 | * @param array $parameters
294 | * @return string
295 | */
296 | protected function replaceGte($message, $attribute, $rule, $parameters)
297 | {
298 | if (is_null($value = $this->getValue($parameters[0]))) {
299 | return str_replace(':value', $parameters[0], $message);
300 | }
301 |
302 | return str_replace(':value', $this->getSize($attribute, $value), $message);
303 | }
304 |
305 | /**
306 | * Replace all place-holders for the lte rule.
307 | *
308 | * @param string $message
309 | * @param string $attribute
310 | * @param string $rule
311 | * @param array $parameters
312 | * @return string
313 | */
314 | protected function replaceLte($message, $attribute, $rule, $parameters)
315 | {
316 | if (is_null($value = $this->getValue($parameters[0]))) {
317 | return str_replace(':value', $parameters[0], $message);
318 | }
319 |
320 | return str_replace(':value', $this->getSize($attribute, $value), $message);
321 | }
322 |
323 | /**
324 | * Replace all place-holders for the required_if rule.
325 | *
326 | * @param string $message
327 | * @param string $attribute
328 | * @param string $rule
329 | * @param array $parameters
330 | * @return string
331 | */
332 | protected function replaceRequiredIf($message, $attribute, $rule, $parameters)
333 | {
334 | $parameters[1] = $this->getDisplayableValue($parameters[0], Arr::get($this->data, $parameters[0]));
335 |
336 | $parameters[0] = $this->getDisplayableAttribute($parameters[0]);
337 |
338 | return str_replace([':other', ':value'], $parameters, $message);
339 | }
340 |
341 | /**
342 | * Replace all place-holders for the required_unless rule.
343 | *
344 | * @param string $message
345 | * @param string $attribute
346 | * @param string $rule
347 | * @param array $parameters
348 | * @return string
349 | */
350 | protected function replaceRequiredUnless($message, $attribute, $rule, $parameters)
351 | {
352 | $other = $this->getDisplayableAttribute($parameters[0]);
353 |
354 | $values = [];
355 |
356 | foreach (array_slice($parameters, 1) as $value) {
357 | $values[] = $this->getDisplayableValue($parameters[0], $value);
358 | }
359 |
360 | return str_replace([':other', ':values'], [$other, implode(', ', $values)], $message);
361 | }
362 |
363 | /**
364 | * Replace all place-holders for the same rule.
365 | *
366 | * @param string $message
367 | * @param string $attribute
368 | * @param string $rule
369 | * @param array $parameters
370 | * @return string
371 | */
372 | protected function replaceSame($message, $attribute, $rule, $parameters)
373 | {
374 | return str_replace(':other', $this->getDisplayableAttribute($parameters[0]), $message);
375 | }
376 |
377 | /**
378 | * Replace all place-holders for the before rule.
379 | *
380 | * @param string $message
381 | * @param string $attribute
382 | * @param string $rule
383 | * @param array $parameters
384 | * @return string
385 | */
386 | protected function replaceBefore($message, $attribute, $rule, $parameters)
387 | {
388 | if (! strtotime($parameters[0])) {
389 | return str_replace(':date', $this->getDisplayableAttribute($parameters[0]), $message);
390 | }
391 |
392 | return str_replace(':date', $this->getDisplayableValue($attribute, $parameters[0]), $message);
393 | }
394 |
395 | /**
396 | * Replace all place-holders for the before_or_equal rule.
397 | *
398 | * @param string $message
399 | * @param string $attribute
400 | * @param string $rule
401 | * @param array $parameters
402 | * @return string
403 | */
404 | protected function replaceBeforeOrEqual($message, $attribute, $rule, $parameters)
405 | {
406 | return $this->replaceBefore($message, $attribute, $rule, $parameters);
407 | }
408 |
409 | /**
410 | * Replace all place-holders for the after rule.
411 | *
412 | * @param string $message
413 | * @param string $attribute
414 | * @param string $rule
415 | * @param array $parameters
416 | * @return string
417 | */
418 | protected function replaceAfter($message, $attribute, $rule, $parameters)
419 | {
420 | return $this->replaceBefore($message, $attribute, $rule, $parameters);
421 | }
422 |
423 | /**
424 | * Replace all place-holders for the after_or_equal rule.
425 | *
426 | * @param string $message
427 | * @param string $attribute
428 | * @param string $rule
429 | * @param array $parameters
430 | * @return string
431 | */
432 | protected function replaceAfterOrEqual($message, $attribute, $rule, $parameters)
433 | {
434 | return $this->replaceBefore($message, $attribute, $rule, $parameters);
435 | }
436 |
437 | /**
438 | * Replace all place-holders for the date_equals rule.
439 | *
440 | * @param string $message
441 | * @param string $attribute
442 | * @param string $rule
443 | * @param array $parameters
444 | * @return string
445 | */
446 | protected function replaceDateEquals($message, $attribute, $rule, $parameters)
447 | {
448 | return $this->replaceBefore($message, $attribute, $rule, $parameters);
449 | }
450 |
451 | /**
452 | * Replace all place-holders for the dimensions rule.
453 | *
454 | * @param string $message
455 | * @param string $attribute
456 | * @param string $rule
457 | * @param array $parameters
458 | * @return string
459 | */
460 | protected function replaceDimensions($message, $attribute, $rule, $parameters)
461 | {
462 | $parameters = $this->parseNamedParameters($parameters);
463 |
464 | if (is_array($parameters)) {
465 | foreach ($parameters as $key => $value) {
466 | $message = str_replace(':'.$key, $value, $message);
467 | }
468 | }
469 |
470 | return $message;
471 | }
472 |
473 | /**
474 | * Replace all place-holders for the ends_with rule.
475 | *
476 | * @param string $message
477 | * @param string $attribute
478 | * @param string $rule
479 | * @param array $parameters
480 | * @return string
481 | */
482 | protected function replaceEndsWith($message, $attribute, $rule, $parameters)
483 | {
484 | foreach ($parameters as &$parameter) {
485 | $parameter = $this->getDisplayableValue($attribute, $parameter);
486 | }
487 |
488 | return str_replace(':values', implode(', ', $parameters), $message);
489 | }
490 |
491 | /**
492 | * Replace all place-holders for the starts_with rule.
493 | *
494 | * @param string $message
495 | * @param string $attribute
496 | * @param string $rule
497 | * @param array $parameters
498 | * @return string
499 | */
500 | protected function replaceStartsWith($message, $attribute, $rule, $parameters)
501 | {
502 | foreach ($parameters as &$parameter) {
503 | $parameter = $this->getDisplayableValue($attribute, $parameter);
504 | }
505 |
506 | return str_replace(':values', implode(', ', $parameters), $message);
507 | }
508 | }
509 |
--------------------------------------------------------------------------------
/src/Validator.php:
--------------------------------------------------------------------------------
1 | initialRules = $rules;
208 | $this->translator = $translator;
209 | $this->customMessages = $messages;
210 | $this->data = $this->parseData($data);
211 | $this->customAttributes = $customAttributes;
212 |
213 | $this->setRules($rules);
214 | }
215 |
216 | /**
217 | * Parse the data array, converting dots to ->.
218 | *
219 | * @param array $data
220 | * @return array
221 | */
222 | public function parseData(array $data)
223 | {
224 | $newData = [];
225 |
226 | foreach ($data as $key => $value) {
227 | if (is_array($value)) {
228 | $value = $this->parseData($value);
229 | }
230 |
231 | // If the data key contains a dot, we will replace it with another character
232 | // sequence so it doesn't interfere with dot processing when working with
233 | // array based validation rules and array_dot later in the validations.
234 | if (Str::contains((string)$key, '.')) {
235 | $newData[str_replace('.', '->', $key)] = $value;
236 | } else {
237 | $newData[$key] = $value;
238 | }
239 | }
240 |
241 | return $newData;
242 | }
243 |
244 | /**
245 | * Add an after validation callback.
246 | *
247 | * @param callable|string $callback
248 | * @return $this
249 | */
250 | public function after($callback)
251 | {
252 | $this->after[] = function () use ($callback) {
253 | return call_user_func_array($callback, [$this]);
254 | };
255 |
256 | return $this;
257 | }
258 |
259 | /**
260 | * Determine if the data passes the validation rules.
261 | *
262 | * @return bool
263 | */
264 | public function passes()
265 | {
266 | $this->messages = new MessageBag;
267 |
268 | [$this->distinctValues, $this->failedRules] = [[], []];
269 |
270 | // We'll spin through each rule, validating the attributes attached to that
271 | // rule. Any error messages will be added to the containers with each of
272 | // the other error messages, returning true if we don't have messages.
273 | foreach ($this->rules as $attribute => $rules) {
274 | $attribute = str_replace('\.', '->', $attribute);
275 |
276 | foreach ($rules as $rule) {
277 | $this->validateAttribute($attribute, $rule);
278 |
279 | if ($this->shouldStopValidating($attribute)) {
280 | break;
281 | }
282 | }
283 | }
284 |
285 | // Here we will spin through all of the "after" hooks on this validator and
286 | // fire them off. This gives the callbacks a chance to perform all kinds
287 | // of other validation that needs to get wrapped up in this operation.
288 | foreach ($this->after as $after) {
289 | call_user_func($after);
290 | }
291 |
292 | return $this->messages->isEmpty();
293 | }
294 |
295 | /**
296 | * Determine if the data fails the validation rules.
297 | *
298 | * @return bool
299 | */
300 | public function fails()
301 | {
302 | return !$this->passes();
303 | }
304 |
305 | /**
306 | * Run the validator's rules against its data.
307 | *
308 | * @return array
309 | *
310 | * @throws \Chunhei2008\Hyperf\Validation\ValidationException
311 | */
312 | public function validate()
313 | {
314 | if ($this->fails()) {
315 | throw new ValidationException($this);
316 | }
317 |
318 | return $this->validated();
319 | }
320 |
321 | /**
322 | * Get the attributes and values that were validated.
323 | *
324 | * @return array
325 | *
326 | * @throws \Chunhei2008\Hyperf\Validation\ValidationException
327 | */
328 | public function validated()
329 | {
330 | if ($this->invalid()) {
331 | throw new ValidationException($this);
332 | }
333 |
334 | $results = [];
335 |
336 | $missingValue = Str::random(10);
337 |
338 | foreach (array_keys($this->getRules()) as $key) {
339 | $value = data_get($this->getData(), $key, $missingValue);
340 |
341 | if ($value !== $missingValue) {
342 | Arr::set($results, $key, $value);
343 | }
344 | }
345 |
346 | return $results;
347 | }
348 |
349 | /**
350 | * Validate a given attribute against a rule.
351 | *
352 | * @param string $attribute
353 | * @param string $rule
354 | * @return void
355 | */
356 | protected function validateAttribute($attribute, $rule)
357 | {
358 | $this->currentRule = $rule;
359 |
360 | [$rule, $parameters] = ValidationRuleParser::parse($rule);
361 |
362 | if ($rule == '') {
363 | return;
364 | }
365 |
366 | // First we will get the correct keys for the given attribute in case the field is nested in
367 | // an array. Then we determine if the given rule accepts other field names as parameters.
368 | // If so, we will replace any asterisks found in the parameters with the correct keys.
369 | if (($keys = $this->getExplicitKeys($attribute)) &&
370 | $this->dependsOnOtherFields($rule)) {
371 | $parameters = $this->replaceAsterisksInParameters($parameters, $keys);
372 | }
373 |
374 | $value = $this->getValue($attribute);
375 |
376 | // If the attribute is a file, we will verify that the file upload was actually successful
377 | // and if it wasn't we will add a failure for the attribute. Files may not successfully
378 | // upload if they are too large based on PHP's settings so we will bail in this case.
379 | if ($value instanceof UploadedFile && !$value->isValid() &&
380 | $this->hasRule($attribute, array_merge($this->fileRules, $this->implicitRules))
381 | ) {
382 | return $this->addFailure($attribute, 'uploaded', []);
383 | }
384 |
385 | // If we have made it this far we will make sure the attribute is validatable and if it is
386 | // we will call the validation method with the attribute. If a method returns false the
387 | // attribute is invalid and we will add a failure message for this failing attribute.
388 | $validatable = $this->isValidatable($rule, $attribute, $value);
389 |
390 | if ($rule instanceof RuleContract) {
391 | return $validatable
392 | ? $this->validateUsingCustomRule($attribute, $value, $rule)
393 | : null;
394 | }
395 |
396 | $method = "validate{$rule}";
397 |
398 | if ($validatable && !$this->$method($attribute, $value, $parameters, $this)) {
399 | $this->addFailure($attribute, $rule, $parameters);
400 | }
401 | }
402 |
403 | /**
404 | * Determine if the given rule depends on other fields.
405 | *
406 | * @param string $rule
407 | * @return bool
408 | */
409 | protected function dependsOnOtherFields($rule)
410 | {
411 | return in_array($rule, $this->dependentRules);
412 | }
413 |
414 | /**
415 | * Get the explicit keys from an attribute flattened with dot notation.
416 | *
417 | * E.g. 'foo.1.bar.spark.baz' -> [1, 'spark'] for 'foo.*.bar.*.baz'
418 | *
419 | * @param string $attribute
420 | * @return array
421 | */
422 | protected function getExplicitKeys($attribute)
423 | {
424 | $pattern = str_replace('\*', '([^\.]+)', preg_quote($this->getPrimaryAttribute($attribute), '/'));
425 |
426 | if (preg_match('/^' . $pattern . '/', $attribute, $keys)) {
427 | array_shift($keys);
428 |
429 | return $keys;
430 | }
431 |
432 | return [];
433 | }
434 |
435 | /**
436 | * Get the primary attribute name.
437 | *
438 | * For example, if "name.0" is given, "name.*" will be returned.
439 | *
440 | * @param string $attribute
441 | * @return string
442 | */
443 | protected function getPrimaryAttribute($attribute)
444 | {
445 | foreach ($this->implicitAttributes as $unparsed => $parsed) {
446 | if (in_array($attribute, $parsed)) {
447 | return $unparsed;
448 | }
449 | }
450 |
451 | return $attribute;
452 | }
453 |
454 | /**
455 | * Replace each field parameter which has asterisks with the given keys.
456 | *
457 | * @param array $parameters
458 | * @param array $keys
459 | * @return array
460 | */
461 | protected function replaceAsterisksInParameters(array $parameters, array $keys)
462 | {
463 | return array_map(function ($field) use ($keys) {
464 | return vsprintf(str_replace('*', '%s', $field), $keys);
465 | }, $parameters);
466 | }
467 |
468 | /**
469 | * Determine if the attribute is validatable.
470 | *
471 | * @param object|string $rule
472 | * @param string $attribute
473 | * @param mixed $value
474 | * @return bool
475 | */
476 | protected function isValidatable($rule, $attribute, $value)
477 | {
478 | return $this->presentOrRuleIsImplicit($rule, $attribute, $value) &&
479 | $this->passesOptionalCheck($attribute) &&
480 | $this->isNotNullIfMarkedAsNullable($rule, $attribute) &&
481 | $this->hasNotFailedPreviousRuleIfPresenceRule($rule, $attribute);
482 | }
483 |
484 | /**
485 | * Determine if the field is present, or the rule implies required.
486 | *
487 | * @param object|string $rule
488 | * @param string $attribute
489 | * @param mixed $value
490 | * @return bool
491 | */
492 | protected function presentOrRuleIsImplicit($rule, $attribute, $value)
493 | {
494 | if (is_string($value) && trim($value) === '') {
495 | return $this->isImplicit($rule);
496 | }
497 |
498 | return $this->validatePresent($attribute, $value) ||
499 | $this->isImplicit($rule);
500 | }
501 |
502 | /**
503 | * Determine if a given rule implies the attribute is required.
504 | *
505 | * @param object|string $rule
506 | * @return bool
507 | */
508 | protected function isImplicit($rule)
509 | {
510 | return $rule instanceof ImplicitRule ||
511 | in_array($rule, $this->implicitRules);
512 | }
513 |
514 | /**
515 | * Determine if the attribute passes any optional check.
516 | *
517 | * @param string $attribute
518 | * @return bool
519 | */
520 | protected function passesOptionalCheck($attribute)
521 | {
522 | if (!$this->hasRule($attribute, ['Sometimes'])) {
523 | return true;
524 | }
525 |
526 | $data = ValidationData::initializeAndGatherData($attribute, $this->data);
527 |
528 | return array_key_exists($attribute, $data)
529 | || array_key_exists($attribute, $this->data);
530 | }
531 |
532 | /**
533 | * Determine if the attribute fails the nullable check.
534 | *
535 | * @param string $rule
536 | * @param string $attribute
537 | * @return bool
538 | */
539 | protected function isNotNullIfMarkedAsNullable($rule, $attribute)
540 | {
541 | if ($this->isImplicit($rule) || !$this->hasRule($attribute, ['Nullable'])) {
542 | return true;
543 | }
544 |
545 | return !is_null(Arr::get($this->data, $attribute, 0));
546 | }
547 |
548 | /**
549 | * Determine if it's a necessary presence validation.
550 | *
551 | * This is to avoid possible database type comparison errors.
552 | *
553 | * @param string $rule
554 | * @param string $attribute
555 | * @return bool
556 | */
557 | protected function hasNotFailedPreviousRuleIfPresenceRule($rule, $attribute)
558 | {
559 | return in_array($rule, ['Unique', 'Exists']) ? !$this->messages->has($attribute) : true;
560 | }
561 |
562 | /**
563 | * Validate an attribute using a custom rule object.
564 | *
565 | * @param string $attribute
566 | * @param mixed $value
567 | * @param \Chunhei2008\Hyperf\Validation\Contracts\Validation\Rule $rule
568 | * @return void
569 | */
570 | protected function validateUsingCustomRule($attribute, $value, $rule)
571 | {
572 | if (!$rule->passes($attribute, $value)) {
573 | $this->failedRules[$attribute][get_class($rule)] = [];
574 |
575 | $messages = $rule->message() ? (array)$rule->message() : [get_class($rule)];
576 |
577 | foreach ($messages as $message) {
578 | $this->messages->add($attribute, $this->makeReplacements(
579 | $message, $attribute, get_class($rule), []
580 | ));
581 | }
582 | }
583 | }
584 |
585 | /**
586 | * Check if we should stop further validations on a given attribute.
587 | *
588 | * @param string $attribute
589 | * @return bool
590 | */
591 | protected function shouldStopValidating($attribute)
592 | {
593 | if ($this->hasRule($attribute, ['Bail'])) {
594 | return $this->messages->has($attribute);
595 | }
596 |
597 | if (isset($this->failedRules[$attribute]) &&
598 | array_key_exists('uploaded', $this->failedRules[$attribute])) {
599 | return true;
600 | }
601 |
602 | // In case the attribute has any rule that indicates that the field is required
603 | // and that rule already failed then we should stop validation at this point
604 | // as now there is no point in calling other rules with this field empty.
605 | return $this->hasRule($attribute, $this->implicitRules) &&
606 | isset($this->failedRules[$attribute]) &&
607 | array_intersect(array_keys($this->failedRules[$attribute]), $this->implicitRules);
608 | }
609 |
610 | /**
611 | * Add a failed rule and error message to the collection.
612 | *
613 | * @param string $attribute
614 | * @param string $rule
615 | * @param array $parameters
616 | * @return void
617 | */
618 | public function addFailure($attribute, $rule, $parameters = [])
619 | {
620 | if (!$this->messages) {
621 | $this->passes();
622 | }
623 |
624 | $this->messages->add($attribute, $this->makeReplacements(
625 | $this->getMessage($attribute, $rule), $attribute, $rule, $parameters
626 | ));
627 |
628 | $this->failedRules[$attribute][$rule] = $parameters;
629 | }
630 |
631 | /**
632 | * Returns the data which was valid.
633 | *
634 | * @return array
635 | */
636 | public function valid()
637 | {
638 | if (!$this->messages) {
639 | $this->passes();
640 | }
641 |
642 | return array_diff_key(
643 | $this->data, $this->attributesThatHaveMessages()
644 | );
645 | }
646 |
647 | /**
648 | * Returns the data which was invalid.
649 | *
650 | * @return array
651 | */
652 | public function invalid()
653 | {
654 | if (!$this->messages) {
655 | $this->passes();
656 | }
657 |
658 | return array_intersect_key(
659 | $this->data, $this->attributesThatHaveMessages()
660 | );
661 | }
662 |
663 | /**
664 | * Generate an array of all attributes that have messages.
665 | *
666 | * @return array
667 | */
668 | protected function attributesThatHaveMessages()
669 | {
670 | return collect($this->messages()->toArray())->map(function ($message, $key) {
671 | return explode('.', $key)[0];
672 | })->unique()->flip()->all();
673 | }
674 |
675 | /**
676 | * Get the failed validation rules.
677 | *
678 | * @return array
679 | */
680 | public function failed()
681 | {
682 | return $this->failedRules;
683 | }
684 |
685 | /**
686 | * Get the message container for the validator.
687 | *
688 | * @return MessageBag
689 | */
690 | public function messages()
691 | {
692 | if (!$this->messages) {
693 | $this->passes();
694 | }
695 |
696 | return $this->messages;
697 | }
698 |
699 | /**
700 | * An alternative more semantic shortcut to the message container.
701 | *
702 | * @return MessageBag
703 | */
704 | public function errors()
705 | {
706 | return $this->messages();
707 | }
708 |
709 | /**
710 | * Get the messages for the instance.
711 | *
712 | * @return MessageBag
713 | */
714 | public function getMessageBag()
715 | {
716 | return $this->messages();
717 | }
718 |
719 | /**
720 | * Determine if the given attribute has a rule in the given set.
721 | *
722 | * @param string $attribute
723 | * @param string|array $rules
724 | * @return bool
725 | */
726 | public function hasRule($attribute, $rules)
727 | {
728 | return !is_null($this->getRule($attribute, $rules));
729 | }
730 |
731 | /**
732 | * Get a rule and its parameters for a given attribute.
733 | *
734 | * @param string $attribute
735 | * @param string|array $rules
736 | * @return array|null
737 | */
738 | protected function getRule($attribute, $rules)
739 | {
740 | if (!array_key_exists($attribute, $this->rules)) {
741 | return;
742 | }
743 |
744 | $rules = (array)$rules;
745 |
746 | foreach ($this->rules[$attribute] as $rule) {
747 | [$rule, $parameters] = ValidationRuleParser::parse($rule);
748 |
749 | if (in_array($rule, $rules)) {
750 | return [$rule, $parameters];
751 | }
752 | }
753 | }
754 |
755 | /**
756 | * Get the data under validation.
757 | *
758 | * @return array
759 | */
760 | public function attributes()
761 | {
762 | return $this->getData();
763 | }
764 |
765 | /**
766 | * Get the data under validation.
767 | *
768 | * @return array
769 | */
770 | public function getData()
771 | {
772 | return $this->data;
773 | }
774 |
775 | /**
776 | * Set the data under validation.
777 | *
778 | * @param array $data
779 | * @return $this
780 | */
781 | public function setData(array $data)
782 | {
783 | $this->data = $this->parseData($data);
784 |
785 | $this->setRules($this->initialRules);
786 |
787 | return $this;
788 | }
789 |
790 | /**
791 | * Get the value of a given attribute.
792 | *
793 | * @param string $attribute
794 | * @return mixed
795 | */
796 | protected function getValue($attribute)
797 | {
798 | return Arr::get($this->data, $attribute);
799 | }
800 |
801 | /**
802 | * Get the validation rules.
803 | *
804 | * @return array
805 | */
806 | public function getRules()
807 | {
808 | return $this->rules;
809 | }
810 |
811 | /**
812 | * Set the validation rules.
813 | *
814 | * @param array $rules
815 | * @return $this
816 | */
817 | public function setRules(array $rules)
818 | {
819 | $this->initialRules = $rules;
820 |
821 | $this->rules = [];
822 |
823 | $this->addRules($rules);
824 |
825 | return $this;
826 | }
827 |
828 | /**
829 | * Parse the given rules and merge them into current rules.
830 | *
831 | * @param array $rules
832 | * @return void
833 | */
834 | public function addRules($rules)
835 | {
836 | // The primary purpose of this parser is to expand any "*" rules to the all
837 | // of the explicit rules needed for the given data. For example the rule
838 | // names.* would get expanded to names.0, names.1, etc. for this data.
839 | $response = (new ValidationRuleParser($this->data))
840 | ->explode($rules);
841 |
842 | $this->rules = array_merge_recursive(
843 | $this->rules, $response->rules
844 | );
845 |
846 | $this->implicitAttributes = array_merge(
847 | $this->implicitAttributes, $response->implicitAttributes
848 | );
849 | }
850 |
851 | /**
852 | * Add conditions to a given field based on a Closure.
853 | *
854 | * @param string|array $attribute
855 | * @param string|array $rules
856 | * @param callable $callback
857 | * @return $this
858 | */
859 | public function sometimes($attribute, $rules, callable $callback)
860 | {
861 | $payload = new Fluent($this->getData());
862 |
863 | if (call_user_func($callback, $payload)) {
864 | foreach ((array)$attribute as $key) {
865 | $this->addRules([$key => $rules]);
866 | }
867 | }
868 |
869 | return $this;
870 | }
871 |
872 | /**
873 | * Register an array of custom validator extensions.
874 | *
875 | * @param array $extensions
876 | * @return void
877 | */
878 | public function addExtensions(array $extensions)
879 | {
880 | if ($extensions) {
881 | $keys = array_map('\Hyperf\Utils\Str::snake', array_keys($extensions));
882 |
883 | $extensions = array_combine($keys, array_values($extensions));
884 | }
885 |
886 | $this->extensions = array_merge($this->extensions, $extensions);
887 | }
888 |
889 | /**
890 | * Register an array of custom implicit validator extensions.
891 | *
892 | * @param array $extensions
893 | * @return void
894 | */
895 | public function addImplicitExtensions(array $extensions)
896 | {
897 | $this->addExtensions($extensions);
898 |
899 | foreach ($extensions as $rule => $extension) {
900 | $this->implicitRules[] = Str::studly($rule);
901 | }
902 | }
903 |
904 | /**
905 | * Register an array of custom implicit validator extensions.
906 | *
907 | * @param array $extensions
908 | * @return void
909 | */
910 | public function addDependentExtensions(array $extensions)
911 | {
912 | $this->addExtensions($extensions);
913 |
914 | foreach ($extensions as $rule => $extension) {
915 | $this->dependentRules[] = Str::studly($rule);
916 | }
917 | }
918 |
919 | /**
920 | * Register a custom validator extension.
921 | *
922 | * @param string $rule
923 | * @param \Closure|string $extension
924 | * @return void
925 | */
926 | public function addExtension($rule, $extension)
927 | {
928 | $this->extensions[Str::snake($rule)] = $extension;
929 | }
930 |
931 | /**
932 | * Register a custom implicit validator extension.
933 | *
934 | * @param string $rule
935 | * @param \Closure|string $extension
936 | * @return void
937 | */
938 | public function addImplicitExtension($rule, $extension)
939 | {
940 | $this->addExtension($rule, $extension);
941 |
942 | $this->implicitRules[] = Str::studly($rule);
943 | }
944 |
945 | /**
946 | * Register a custom dependent validator extension.
947 | *
948 | * @param string $rule
949 | * @param \Closure|string $extension
950 | * @return void
951 | */
952 | public function addDependentExtension($rule, $extension)
953 | {
954 | $this->addExtension($rule, $extension);
955 |
956 | $this->dependentRules[] = Str::studly($rule);
957 | }
958 |
959 | /**
960 | * Register an array of custom validator message replacers.
961 | *
962 | * @param array $replacers
963 | * @return void
964 | */
965 | public function addReplacers(array $replacers)
966 | {
967 | if ($replacers) {
968 | $keys = array_map('\Hyperf\Utils\Str::snake', array_keys($replacers));
969 |
970 | $replacers = array_combine($keys, array_values($replacers));
971 | }
972 |
973 | $this->replacers = array_merge($this->replacers, $replacers);
974 | }
975 |
976 | /**
977 | * Register a custom validator message replacer.
978 | *
979 | * @param string $rule
980 | * @param \Closure|string $replacer
981 | * @return void
982 | */
983 | public function addReplacer($rule, $replacer)
984 | {
985 | $this->replacers[Str::snake($rule)] = $replacer;
986 | }
987 |
988 | /**
989 | * Set the custom messages for the validator.
990 | *
991 | * @param array $messages
992 | * @return $this
993 | */
994 | public function setCustomMessages(array $messages)
995 | {
996 | $this->customMessages = array_merge($this->customMessages, $messages);
997 |
998 | return $this;
999 | }
1000 |
1001 | /**
1002 | * Set the custom attributes on the validator.
1003 | *
1004 | * @param array $attributes
1005 | * @return $this
1006 | */
1007 | public function setAttributeNames(array $attributes)
1008 | {
1009 | $this->customAttributes = $attributes;
1010 |
1011 | return $this;
1012 | }
1013 |
1014 | /**
1015 | * Add custom attributes to the validator.
1016 | *
1017 | * @param array $customAttributes
1018 | * @return $this
1019 | */
1020 | public function addCustomAttributes(array $customAttributes)
1021 | {
1022 | $this->customAttributes = array_merge($this->customAttributes, $customAttributes);
1023 |
1024 | return $this;
1025 | }
1026 |
1027 | /**
1028 | * Set the custom values on the validator.
1029 | *
1030 | * @param array $values
1031 | * @return $this
1032 | */
1033 | public function setValueNames(array $values)
1034 | {
1035 | $this->customValues = $values;
1036 |
1037 | return $this;
1038 | }
1039 |
1040 | /**
1041 | * Add the custom values for the validator.
1042 | *
1043 | * @param array $customValues
1044 | * @return $this
1045 | */
1046 | public function addCustomValues(array $customValues)
1047 | {
1048 | $this->customValues = array_merge($this->customValues, $customValues);
1049 |
1050 | return $this;
1051 | }
1052 |
1053 | /**
1054 | * Set the fallback messages for the validator.
1055 | *
1056 | * @param array $messages
1057 | * @return void
1058 | */
1059 | public function setFallbackMessages(array $messages)
1060 | {
1061 | $this->fallbackMessages = $messages;
1062 | }
1063 |
1064 | /**
1065 | * Get the Presence Verifier implementation.
1066 | *
1067 | * @return \Chunhei2008\Hyperf\Validation\PresenceVerifierInterface
1068 | *
1069 | * @throws \RuntimeException
1070 | *
1071 | *
1072 | */
1073 | public function getPresenceVerifier()
1074 | {
1075 | if (!isset($this->presenceVerifier)) {
1076 | throw new RuntimeException('Presence verifier has not been set.');
1077 | }
1078 |
1079 | return $this->presenceVerifier;
1080 | }
1081 |
1082 | /**
1083 | * Get the Presence Verifier implementation.
1084 | *
1085 | * @param string $connection
1086 | * @return \Chunhei2008\Hyperf\Validation\PresenceVerifierInterface
1087 | *
1088 | * @throws \RuntimeException
1089 | *
1090 | *
1091 | */
1092 | public function getPresenceVerifierFor($connection)
1093 | {
1094 | return tap($this->getPresenceVerifier(), function ($verifier) use ($connection) {
1095 | $verifier->setConnection($connection);
1096 | });
1097 | }
1098 |
1099 | /**
1100 | * Set the Presence Verifier implementation.
1101 | *
1102 | * @param \Chunhei2008\Hyperf\Validation\PresenceVerifierInterface $presenceVerifier
1103 | * @return void
1104 | */
1105 | public function setPresenceVerifier(PresenceVerifierInterface $presenceVerifier)
1106 | {
1107 | $this->presenceVerifier = $presenceVerifier;
1108 | }
1109 |
1110 | /**
1111 | * Get the Translator implementation.
1112 | *
1113 | * @return \Chunhei2008\Hyperf\Translation\Contracts\Translator
1114 | */
1115 | public function getTranslator()
1116 | {
1117 | return $this->translator;
1118 | }
1119 |
1120 | /**
1121 | * Set the Translator implementation.
1122 | *
1123 | * @param \Chunhei2008\Hyperf\Translation\Contracts\Translator $translator
1124 | * @return void
1125 | */
1126 | public function setTranslator(Translator $translator)
1127 | {
1128 | $this->translator = $translator;
1129 | }
1130 |
1131 | /**
1132 | * Set the IoC container instance.
1133 | *
1134 | * @param Container $container
1135 | * @return void
1136 | */
1137 | public function setContainer(Container $container)
1138 | {
1139 | $this->container = $container;
1140 | }
1141 |
1142 | /**
1143 | * Call a custom validator extension.
1144 | *
1145 | * @param string $rule
1146 | * @param array $parameters
1147 | * @return bool|null
1148 | */
1149 | protected function callExtension($rule, $parameters)
1150 | {
1151 | $callback = $this->extensions[$rule];
1152 |
1153 | if (is_callable($callback)) {
1154 | return call_user_func_array($callback, $parameters);
1155 | } elseif (is_string($callback)) {
1156 | return $this->callClassBasedExtension($callback, $parameters);
1157 | }
1158 | }
1159 |
1160 | /**
1161 | * Call a class based validator extension.
1162 | *
1163 | * @param string $callback
1164 | * @param array $parameters
1165 | * @return bool
1166 | */
1167 | protected function callClassBasedExtension($callback, $parameters)
1168 | {
1169 | [$class, $method] = Str::parseCallback($callback, 'validate');
1170 |
1171 | return call_user_func_array([$this->container->make($class), $method], $parameters);
1172 | }
1173 |
1174 | /**
1175 | * Handle dynamic calls to class methods.
1176 | *
1177 | * @param string $method
1178 | * @param array $parameters
1179 | * @return mixed
1180 | *
1181 | * @throws \BadMethodCallException
1182 | */
1183 | public function __call($method, $parameters)
1184 | {
1185 | $rule = Str::snake(substr($method, 8));
1186 |
1187 | if (isset($this->extensions[$rule])) {
1188 | return $this->callExtension($rule, $parameters);
1189 | }
1190 |
1191 | throw new BadMethodCallException(sprintf(
1192 | 'Method %s::%s does not exist.', static::class, $method
1193 | ));
1194 | }
1195 | }
1196 |
--------------------------------------------------------------------------------