diff --git a/.phpstorm.meta.php b/.phpstorm.meta.php new file mode 100644 index 0000000000000000000000000000000000000000..aa3d148ab3cabe5a56084e8f5f132be3e24d4eaa --- /dev/null +++ b/.phpstorm.meta.php @@ -0,0 +1,10 @@ + "$1[]"])); +} + +# end of file diff --git a/src/main/Annotation/NumberScope.php b/src/main/Annotation/NumberScope.php new file mode 100644 index 0000000000000000000000000000000000000000..e82df9eb37fb8e2bf5c958c7c52489212d4e683b --- /dev/null +++ b/src/main/Annotation/NumberScope.php @@ -0,0 +1,65 @@ +message)) { + return $this->message; + } + $list = []; + if ($this->max !== null) { + $list[] = "不能大于 {$this->max}"; + } + if ($this->min !== null) { + $list[] = "不能小于 {$this->min}"; + } + return ReflectionUtils::getReflectDesc($reflection) . " " . implode(" 且", $list); + } + + /** + * @inheritDoc + */ + public function isValid(mixed $value): bool + { + if ($value === null) { + return true; + } + if ($this->max !== null && $value > $this->max) { + return false; + } + if ($this->min !== null && $value < $this->min) { + return false; + } + return true; + } +} + +# end of file diff --git a/src/main/Annotation/Pattern.php b/src/main/Annotation/Pattern.php new file mode 100644 index 0000000000000000000000000000000000000000..61354ea3b872d37c59949821eb83f877c490c696 --- /dev/null +++ b/src/main/Annotation/Pattern.php @@ -0,0 +1,49 @@ +message)) { + return $this->message; + } + return ReflectionUtils::getReflectDesc($reflection) . " 不匹配要求的正则表达式 {$this->pattern}"; + } + + /** + * @inheritDoc + */ + public function isValid(mixed $value): bool + { + if ($value === null) { + return true; + } + return boolval(preg_match($this->pattern, strval($value))); + } +} + +# end of file diff --git a/src/main/Annotation/Size.php b/src/main/Annotation/Size.php new file mode 100644 index 0000000000000000000000000000000000000000..9ccc4d89bb7964aac5982cdc9e332631badf0a3d --- /dev/null +++ b/src/main/Annotation/Size.php @@ -0,0 +1,70 @@ +message)) { + return $this->message; + } + $list = []; + if ($this->sizeMax !== null) { + $list[] = "不能大于 {$this->sizeMax}"; + } + if ($this->sizeMin !== null) { + $list[] = "不能小于 {$this->sizeMin}"; + } + return "对象属性 {$reflection->class}::{$reflection->name} 的 size " . implode(" 且", $list); + } + + /** + * @inheritDoc + */ + public function isValid(mixed $value): bool + { + if ($value === null) { + return true; + } + if (is_array($value) || is_object($value)) { + $size = count($value); + } else { + $size = strlen(strval($value)); + } + + if ($this->sizeMax !== null && $size > $this->sizeMax) { + return false; + } + if ($this->sizeMin !== null && $size < $this->sizeMin) { + return false; + } + return true; + } +} + +# end of file diff --git a/src/main/Base/ValidAnnoInterface.php b/src/main/Base/ValidAnnoInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..301b27e5bfc6ceafc44521bdfaf3deae96306a74 --- /dev/null +++ b/src/main/Base/ValidAnnoInterface.php @@ -0,0 +1,32 @@ +reflection; + } + + /** + * @return mixed + */ + public function getValue(): mixed + { + return $this->value; + } + + /** + * @return ValidAnnoInterface + */ + public function getInvalid(): ValidAnnoInterface + { + return $this->invalid; + } } # end of file diff --git a/src/main/Utils/AnnotationUtils.php b/src/main/Utils/AnnotationUtils.php new file mode 100644 index 0000000000000000000000000000000000000000..b1a9e133a68dd9571001ddaeede09bb935165ea2 --- /dev/null +++ b/src/main/Utils/AnnotationUtils.php @@ -0,0 +1,71 @@ +getAttributes(); + $list = []; + foreach ($attrs as $attr) { + if (!class_exists($attr->getName())) { + continue; + } + if ($className && !is_a($attr->getName(), $className, true)) { + continue; + } + $list[] = $attr->newInstance(); + } + return $list; + } + + /** + * - + * @param ReflectionProperty|ReflectionClass|ReflectionParameter|ReflectionFunctionAbstract|ReflectionClassConstant $reflection + * 反射 + * @param ?string $className 注解要求的类名 + * @psalm-param class-string $className + * @psalm-return ?T 注解对象 + * @template T + */ + public static function getAttrObj( + ReflectionProperty|ReflectionClass|ReflectionParameter|ReflectionFunctionAbstract|ReflectionClassConstant $reflection, + string $className = null + ): ?object { + $attrs = $reflection->getAttributes(); + foreach ($attrs as $attr) { + if (!class_exists($attr->getName())) { + continue; + } + if ($className && !is_a($attr->getName(), $className, true)) { + continue; + } + return $attr->newInstance(); + } + return null; + } +} + +# end of file diff --git a/src/main/Utils/ReflectionUtils.php b/src/main/Utils/ReflectionUtils.php new file mode 100644 index 0000000000000000000000000000000000000000..95fe1895ff65b5692c84c62df63069ea9a112f47 --- /dev/null +++ b/src/main/Utils/ReflectionUtils.php @@ -0,0 +1,36 @@ +name)) { + $name = $reflector->name; + } elseif (method_exists($reflector, "getName")) { + $name = $reflector->getName(); + } else { + $name = strval($reflector); + } + if (isset($reflector->class)) { + $name = "{$reflector->class}::{$name}"; + } + return $name; + } +} + +# end of file diff --git a/src/main/ValidationInterface.php b/src/main/ValidationInterface.php index 4f32bb97b07890cd1eb53ea8d163eaf17e6000e3..261cb73b63dffb45da9591637dcde57db857ba51 100644 --- a/src/main/ValidationInterface.php +++ b/src/main/ValidationInterface.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace DreamCat\ObjectValid; use DreamCat\ObjectValid\Exception\CheckErrorException; +use ReflectionParameter; /** * 对象属性检查接口 @@ -14,11 +15,20 @@ interface ValidationInterface { /** * 检查数据是否合格 - * @param object $object + * @param object $object 要检查的对象 * @return void - * @throws CheckErrorException + * @throws CheckErrorException 检查不通过时抛出异常 */ - public function valid(object $object): void; + public function validObject(object $object): void; + + /** + * 检查参数是否合格 + * @param ReflectionParameter $reflectionParameter 要检查的参数反射 + * @param mixed $paramValue 参数的值 + * @return void + * @throws CheckErrorException 检查不通过时抛出异常 + */ + public function validParam(ReflectionParameter $reflectionParameter, mixed $paramValue): void; } # end of file diff --git a/src/main/Validor/Validator.php b/src/main/Validor/Validator.php new file mode 100644 index 0000000000000000000000000000000000000000..a6dbd1821d1314a3063728188c2b46ce614b2842 --- /dev/null +++ b/src/main/Validor/Validator.php @@ -0,0 +1,115 @@ +getProperties($filter) as $property) { + $this->checkProperty($object, $property); + } + } + + /** + * @inheritDoc + */ + public function validParam(ReflectionParameter $reflectionParameter, mixed $paramValue): void + { + $this->checkValue($paramValue, $reflectionParameter); + } + + /** + * 检查值是否符合注解要求 + * @param mixed $value + * @param ReflectionParameter|ReflectionProperty $reflection + * @return void + */ + protected function checkValue(mixed $value, ReflectionParameter|ReflectionProperty $reflection): void + { + foreach (AnnotationUtils::getAttrObjs($reflection, ValidAnnoInterface::class) as $attrObj) { + if ($attrObj->isValid($value)) { + continue; + } + $msg = $attrObj->errorMessage($reflection, $value); + if (strlen($msg)) { + throw new CheckErrorException($reflection, $value, $attrObj, $msg); + } + } + $this->recursionCheck($value); + } + + /** + * 检查属性的值 + * @param object $object 对象 + * @param ReflectionProperty $reflection 要检查的属性反射 + * @return void + */ + protected function checkProperty(object $object, ReflectionProperty $reflection): void + { + $value = $this->getPropertyValue($object, $reflection); + $this->checkValue($value, $reflection); + } + + /** + * 获取属性的值 + * @param object $object 对象 + * @param ReflectionProperty $property 属性反射 + * @return mixed 属性值 + */ + protected function getPropertyValue(object $object, ReflectionProperty $property): mixed + { + $allow = $property->isPublic(); + if (!$allow) { + $property->setAccessible(true); + } + if ($property->isInitialized($object)) { + $value = $property->getValue($object); + } else { + $value = null; + } + if (!$allow) { + $property->setAccessible(false); + } + return $value; + } + + /** + * 递归检查 + * @param mixed $value 要检查的值 + * @return void + */ + protected function recursionCheck(mixed $value): void + { + if (is_array($value)) { + foreach ($value as $item) { + $this->recursionCheck($item); + } + } elseif (is_object($value)) { + $this->validObject($value); + } + } +} + +# end of file diff --git a/src/test/Cases/FnValidatorTest.php b/src/test/Cases/FnValidatorTest.php new file mode 100644 index 0000000000000000000000000000000000000000..c1454ce2fe84082ee4aa55e37dab525167a765a7 --- /dev/null +++ b/src/test/Cases/FnValidatorTest.php @@ -0,0 +1,42 @@ + 50]), + 35, + ]; + try { + foreach ($ref->getParameters() as $idx => $parameter) { + $param = $params[$idx]; + $v->validParam($parameter, $param); + } + } catch (CheckErrorException $exception) { + self::assertEquals("v 不能大于 30", $exception->getMessage()); + self::assertEquals($params[1], $exception->getValue()); + self::assertEquals(NumberScope::class, get_class($exception->getInvalid())); + self::assertEquals($ref->getParameters()[1], $exception->getReflection()); + } + } +} + +# end of file diff --git a/src/test/Cases/UtilsTest.php b/src/test/Cases/UtilsTest.php new file mode 100644 index 0000000000000000000000000000000000000000..2965961e6cc88b0dab619585f3edff1a27aaf55f --- /dev/null +++ b/src/test/Cases/UtilsTest.php @@ -0,0 +1,64 @@ + 50, + "str" => "aabce", + "ary" => [ + 3, + 5, + 5, + 3, + ], + ]); + $validator = new Validator(); + $validator->validObject($pojo); + self::assertEquals($pojo, $pojo); + } + + /** + * - + * @return void + * @dataProvider errorData + */ + public function testError(object $obj, string $expectedError) + { + $this->expectException(CheckErrorException::class); + $this->expectErrorMessage($expectedError); + + $validator = new Validator(); + $validator->validObject($obj); + } + + #[Pure] + public function errorData(): array + { + return [ + [ + DemoPojo::builder(["value" => 100]), + DemoPojo::class . "::value 不能大于 55 且不能小于 15", + ], + [ + DemoPojo::builder(["value" => 1]), + DemoPojo::class . "::value 不能大于 55 且不能小于 15", + ], + [ + DemoPojo::builder(["withMsgInt" => 100]), + "太大了", + ], + [ + DemoPojo::builder(["str" => "c"]), + DemoPojo::class . "::str 不匹配要求的正则表达式 #a#", + ], + [ + DemoPojo::builder(["withMsgStr" => "c"]), + "bbc", + ], + [ + DemoPojo::builder(["withMsgStr" => "a"]), + "aa", + ], + [ + DemoPojo::builder(["withMsgStr" => "aacerqwer"]), + "aa", + ], + [ + DemoPojo::builder( + [ + "ary" => [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + ], + ] + ), + DemoPojo::class . "::ary 的 size 不能大于 5 且不能小于 1", + ], + [ + DemoPojo::builder( + [ + "ary" => [ + DemoPojo::builder(["value" => 1]), + ], + ] + ), + DemoPojo::class . "::value 不能大于 55 且不能小于 15", + ], + ]; + } +} + +# end of file diff --git a/src/test/Helper/DemoPojo.php b/src/test/Helper/DemoPojo.php new file mode 100644 index 0000000000000000000000000000000000000000..339131ce23e36e73a4a389f4a10d1f54c71bcaec --- /dev/null +++ b/src/test/Helper/DemoPojo.php @@ -0,0 +1,46 @@ + $val) { + /** @noinspection PhpVariableVariableInspection */ + $obj->$key = $val; + } + return $obj; + } +} + +# end of file