├── 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 | 2 | 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 | --------------------------------------------------------------------------------