From 1f39a6393b9f0b2c1e69b246adf88bd927418d1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=99=9E=E7=81=AA?= <995645888@qq.com> Date: Fri, 28 May 2021 16:31:10 +0800 Subject: [PATCH] =?UTF-8?q?[A]=20=E5=A2=9E=E5=8A=A0=E8=BF=87=E6=BB=A4?= =?UTF-8?q?=E5=99=A8=EF=BC=8C=E7=94=A8=E4=BA=8E=E5=A4=84=E7=90=86=E9=AA=8C?= =?UTF-8?q?=E8=AF=81=E5=90=8E=E7=9A=84=E6=95=B0=E6=8D=AE=20[U]=20=E4=BF=AE?= =?UTF-8?q?=E6=94=B9=E7=A8=8B=E5=BA=8F=E8=BF=90=E8=A1=8C=E6=97=B6=E7=9A=84?= =?UTF-8?q?=E5=BC=82=E5=B8=B8=E7=B1=BB=E4=B8=BAValidateRuntimeException?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Exception/ValidateRuntimeException.php | 19 ++++ src/Support/Concerns/FilterInterface.php | 18 ++++ src/Support/ValidateScene.php | 32 ++++++ src/Validate.php | 108 ++++++++++++++++----- tests/Test/TestDataFilter.php | 104 ++++++++++++++++++++ 5 files changed, 257 insertions(+), 24 deletions(-) create mode 100644 src/Exception/ValidateRuntimeException.php create mode 100644 src/Support/Concerns/FilterInterface.php create mode 100644 tests/Test/TestDataFilter.php diff --git a/src/Exception/ValidateRuntimeException.php b/src/Exception/ValidateRuntimeException.php new file mode 100644 index 0000000..ee09e6c --- /dev/null +++ b/src/Exception/ValidateRuntimeException.php @@ -0,0 +1,19 @@ + + * + * This is not a free software + * Using it under the license terms + * visited https://www.w7.cc for more details + */ + +namespace W7\Validate\Exception; + +use RuntimeException; + +class ValidateRuntimeException extends RuntimeException +{ +} diff --git a/src/Support/Concerns/FilterInterface.php b/src/Support/Concerns/FilterInterface.php new file mode 100644 index 0000000..fb23c88 --- /dev/null +++ b/src/Support/Concerns/FilterInterface.php @@ -0,0 +1,18 @@ + + * + * This is not a free software + * Using it under the license terms + * visited https://www.w7.cc for more details + */ + +namespace W7\Validate\Support\Concerns; + +interface FilterInterface +{ + public function handle($value); +} diff --git a/src/Support/ValidateScene.php b/src/Support/ValidateScene.php index 6fd0c54..d02dc17 100644 --- a/src/Support/ValidateScene.php +++ b/src/Support/ValidateScene.php @@ -15,6 +15,7 @@ namespace W7\Validate\Support; use Closure; use Illuminate\Support\Arr; use RuntimeException; +use W7\Validate\Support\Concerns\FilterInterface; use W7\Validate\Support\Rule\BaseRule; use W7\Validate\Support\Storage\ValidateCollection; @@ -26,6 +27,7 @@ use W7\Validate\Support\Storage\ValidateCollection; * @property-read array $befores Methods to be executed before this validate * @property-read array $afters Methods to be executed after this validate * @property-read array $defaults This validation requires a default value for the value + * @property-read array $filters The filter. This can be a global function name, anonymous function, etc. * @property-read bool $eventPriority Event Priority */ class ValidateScene extends RuleManagerScene @@ -59,6 +61,12 @@ class ValidateScene extends RuleManagerScene * @var array */ private $defaults = []; + + /** + * The filter. This can be a global function name, anonymous function, etc. + * @var array + */ + private $filters = []; /** * Event Priority @@ -182,6 +190,30 @@ class ValidateScene extends RuleManagerScene return $this; } + /** + * Set a filter for the specified field + * + * Filter is a data processor. + * It invokes the specified filter callback to process the attribute value + * and save the processed value back to the attribute. + * @param string $field Name of the data field to be processed + * @param string|callable|Closure|FilterInterface $callback The filter. This can be a global function name, anonymous function, etc. + * The filter must be a valid PHP callback with the following signature: + * + * function foo($value) { + * // compute $newValue here + * return $newValue; + * } + * + * Many PHP functions qualify this signature (e.g. `trim()`). + * @return $this + */ + public function filter(string $field, $callback): ValidateScene + { + $this->filters[$field] = $callback; + return $this; + } + /** * Set event priority * diff --git a/src/Validate.php b/src/Validate.php index 15ebd0d..7e51ae0 100644 --- a/src/Validate.php +++ b/src/Validate.php @@ -13,10 +13,14 @@ namespace W7\Validate; use Closure; +use Illuminate\Support\Str; use Illuminate\Validation\Factory; +use Illuminate\Validation\ValidationData; use Illuminate\Validation\ValidationException; use LogicException; use W7\Validate\Exception\ValidateException; +use W7\Validate\Exception\ValidateRuntimeException; +use W7\Validate\Support\Concerns\FilterInterface; use W7\Validate\Support\Concerns\MessageProviderInterface; use W7\Validate\Support\Event\ValidateEventAbstract; use W7\Validate\Support\MessageProvider; @@ -86,6 +90,12 @@ class Validate extends RuleManager */ private $defaults = []; + /** + * Filters to be passed for this validation + * @var array + */ + private $filters = []; + /** * Error Message Provider * @var MessageProviderInterface @@ -137,9 +147,12 @@ class Validate extends RuleManager $this->init(); $this->checkData = $data; $this->addEvent($this->event); - $this->defaults = array_merge($this->default, $this->defaults); $rule = $this->getCheckRules($this->getInitialRules()); - $data = $this->handleDefault($data, $rule); + $fields = array_keys($rule); + $this->defaults = array_merge($this->default, $this->defaults); + $this->filters = array_merge($this->filter, $this->filters); + $data = $this->handleDefault($data, $fields); + if ($this->filled) { $rule = $this->addFilledRule($rule); } @@ -157,6 +170,7 @@ class Validate extends RuleManager } $data = $this->getValidationFactory()->make($data, $rule, $this->message, $this->customAttributes)->validate(); + $data = $this->handlerFilter($data, $fields); if ($this->eventPriority) { $this->handleCallback($data, 2); @@ -204,6 +218,7 @@ class Validate extends RuleManager $this->afters = array_merge($this->afters, $scene->afters); $this->befores = array_merge($this->befores, $scene->befores); $this->defaults = array_merge($this->defaults, $scene->defaults); + $this->filters = array_merge($this->filters, $scene->filters); $this->eventPriority = $scene->eventPriority; return $scene->getRules(); } @@ -333,26 +348,83 @@ class Validate extends RuleManager throw new ValidateException($message, 403); } } else { - throw new ValidateException('Event error or nonexistence'); + throw new ValidateRuntimeException('Event error or nonexistence'); } } } /** - * Processing the set defaults + * Filters for processing settings * * @param array $data - * @param array $rule + * @param array $fields * @return array */ - private function handleDefault(array $data, array $rule): array + private function handlerFilter(array $data, array $fields): array + { + if (empty($this->filters)) { + return $data; + } + + $newData = validate_collect($data); + $filters = array_intersect_key($this->filters, array_flip($fields)); + foreach ($filters as $field => $callback) { + if (false !== strpos($field, '*')) { + $flatData = ValidationData::initializeAndGatherData($field, $data); + $pattern = str_replace('\*', '[^\.]*', preg_quote($field)); + foreach ($flatData as $key => $value) { + if (Str::startsWith($key, $field) || preg_match('/^' . $pattern . '\z/', $key)) { + $this->filterValue($key, $callback, $newData); + } + } + } else { + $this->filterValue($field, $callback, $newData); + } + } + + return $newData->toArray(); + } + + /** + * Filter the given value + * + * @param string $field Name of the data field to be processed + * @param callable|Closure|FilterInterface $callback The filter. This can be a global function name, anonymous function, etc. + * @param ValidateCollection $data + */ + private function filterValue(string $field, $callback, ValidateCollection $data) + { + $value = $data->get($field); + + if (is_callable($callback)) { + $value = call_user_func($callback, $value); + } elseif (class_exists($callback) && is_subclass_of($callback, FilterInterface::class)) { + /** @var FilterInterface $filter */ + $filter = new $callback; + $value = $filter->handle($value); + } elseif (is_string($callback) && method_exists($this, 'filter' . ucfirst($callback))) { + $value = call_user_func([$this, 'filter' . ucfirst($callback)], $value); + } else { + throw new ValidateRuntimeException('The provided filter is wrong'); + } + + $data->set($field, $value); + } + + /** + * Defaults for processing settings + * + * @param array $data + * @param array $fields + * @return array + */ + private function handleDefault(array $data, array $fields): array { if (empty($this->defaults)) { return $data; } $newData = validate_collect($data); - $fields = array_keys($rule); $defaults = array_intersect_key($this->defaults, array_flip($fields)); foreach ($defaults as $field => $value) { // Skip array members @@ -374,22 +446,9 @@ class Validate extends RuleManager * Applying default settings to data * * @param string $field Name of the data field to be processed - * @param callable|Closure|mixed $callback the default value or an anonymous function that returns the default value which will - * be assigned to the attributes being validated if they are empty. The signature of the anonymous function - * should be as follows,The anonymous function has two parameters: - * - * - * e.g: - * - * function($value,$originalData){ - * return $value; - * } - * - * @param ValidateCollection $data Data to be processed - * @param bool $any Whether to handle arbitrary values, default only handle values that are not null + * @param callable|Closure|mixed $callback The default value or an anonymous function that returns the default value which will + * @param ValidateCollection $data Data to be processed + * @param bool $any Whether to handle arbitrary values, default only handle values that are not null */ private function setDefaultData(string $field, $callback, ValidateCollection $data, bool $any = false) { @@ -418,6 +477,7 @@ class Validate extends RuleManager $this->afters = []; $this->befores = []; $this->defaults = []; + $this->filters = []; $this->eventPriority = true; } @@ -439,7 +499,7 @@ class Validate extends RuleManager $this->setMessageProvider($messageProvider); return $this; } else { - throw new ValidateException('The provided message processor needs to implement the MessageProviderInterface interface'); + throw new ValidateRuntimeException('The provided message processor needs to implement the MessageProviderInterface interface'); } return $this; diff --git a/tests/Test/TestDataFilter.php b/tests/Test/TestDataFilter.php new file mode 100644 index 0000000..6766882 --- /dev/null +++ b/tests/Test/TestDataFilter.php @@ -0,0 +1,104 @@ + + * + * This is not a free software + * Using it under the license terms + * visited https://www.w7.cc for more details + */ + +namespace W7\Tests\Test; + +use W7\Tests\Material\BaseTestValidate; +use W7\Validate\Support\Concerns\FilterInterface; +use W7\Validate\Validate; + +class UniqueFilter implements FilterInterface +{ + public function handle($value) + { + return array_unique($value); + } +} +class TestDataFilter extends BaseTestValidate +{ + public function testSetFilterIsSystemMethod() + { + $v = new class extends Validate { + protected $rule = [ + 'id' => 'required|numeric' + ]; + + protected $filter = [ + 'id' => 'intval' + ]; + }; + + $data = $v->check(['id' => '1']); + + $this->assertTrue(1 === $data['id']); + } + + public function testSetFilterIsClassMethod() + { + $v = new class extends Validate { + protected $rule = [ + 'id' => 'required' + ]; + + protected $filter = [ + 'id' => 'toArray' + ]; + + public function filterToArray($value) + { + return explode(',', $value); + } + }; + + $data = $v->check(['id' => '1,2,3,4,5']); + + $this->assertEquals([1, 2, 3, 4, 5], $data['id']); + } + + public function testSetFilterIsFilterClass() + { + $v = new class extends Validate { + protected $rule = [ + 'id' => 'required|array', + 'id.*' => 'numeric' + ]; + + protected $filter = [ + 'id' => UniqueFilter::class + ]; + }; + + $data = $v->check(['id' => [1, 1, 2, 3, 4, 4, 5, 6, 7]]); + + $this->assertEquals([1, 2, 3, 4, 5, 6, 7], array_values($data['id'])); + } + + public function testSetFilterForArrayField() + { + $v = new class extends Validate { + protected $rule = [ + 'id' => 'required|array', + 'id.*' => 'numeric' + ]; + + protected $filter = [ + 'id.*' => 'intval' + ]; + }; + + $data = $v->check(['id' => ['1', '2', 3, '4']]); + + foreach ($data['id'] as $id) { + $this->assertEquals('integer', gettype($id)); + } + } +} -- Gitee