diff --git a/benchmarks/SerializeBench.php b/benchmarks/SerializeBench.php index 448358c..b390d4b 100644 --- a/benchmarks/SerializeBench.php +++ b/benchmarks/SerializeBench.php @@ -83,7 +83,7 @@ public function setupObject(): void Revs(500), Iterations(5), BeforeMethods([ 'setupObjectCreation']), - Assert('mode(variant.time.avg) < 120 microseconds +/- 5%') + Assert('mode(variant.time.avg) < 130 microseconds +/- 5%') ] public function benchObjectCreation(): void { diff --git a/docs/en/SUMMARY.md b/docs/en/SUMMARY.md index 4dd73b3..65ea987 100644 --- a/docs/en/SUMMARY.md +++ b/docs/en/SUMMARY.md @@ -11,6 +11,7 @@ * [Array Object Mapping](mapper/array-mapper.md) * [Union Type Mapping](mapper/union-mapper.md) * [Output Format](mapper/out.md) +* [Validation](mapper/validate.md) ## Annotation Usage diff --git a/docs/en/mapper/out.md b/docs/en/mapper/out.md index 2e4c2cc..11b8fcb 100644 --- a/docs/en/mapper/out.md +++ b/docs/en/mapper/out.md @@ -27,6 +27,7 @@ $user = User::from([ ### Outputting the Object ```php + // $user is an object by default echo $user->name; // Output: Jon echo $user->age; // Output: 30 diff --git a/docs/en/mapper/validate.md b/docs/en/mapper/validate.md new file mode 100644 index 0000000..80779f3 --- /dev/null +++ b/docs/en/mapper/validate.md @@ -0,0 +1,61 @@ +## Parameter Validation + +### Validation via the `validate` Method + +The `Serialize` class provides a `validate` method that is automatically called when an object is created using the `from` method. You can implement custom data validation logic within this method. + +Here's an example: + +```php +use Astral\Serialize\Serialize; + +class TestConstructValidationFromSerialize extends Serialize +{ + public string $type_string; + + /** + * Data validation method + * Automatically called after object creation via the from method + */ + public function validate(): void + { + // Validate the value of the type_string property + if ($this->type_string !== '123') { + throw new Exception('type_string must be equal to 123'); + } + + // You can also modify property values + $this->type_string = '234'; + } +} +``` + +### Validation in `__construct` + +When creating objects directly using the `__construct` method, you can implement parameter validation logic in the constructor. However, note that this approach can only access properties defined in the constructor and cannot access other properties. + +```php +use Astral\Serialize\Serialize; + +class TestConstructFromSerialize extends Serialize +{ + // Note: This property cannot be accessed in the constructor + // If you need to validate this property, use the validate method + public string $not_validate_string; + + /** + * Parameter validation in the constructor + * @param string $type_string The input string parameter + */ + public function __construct( + public string $type_string, + ) { + // Validate the input parameter + if ($this->type_string !== '123') { + throw new Exception('type_string must be equal to 123'); + } + + // Modify the property value + $this->type_string = '234'; + } +} \ No newline at end of file diff --git a/docs/zh/SUMMARY.md b/docs/zh/SUMMARY.md index 0beb677..8c93af1 100644 --- a/docs/zh/SUMMARY.md +++ b/docs/zh/SUMMARY.md @@ -11,6 +11,8 @@ * [数组对象转换](mapper/array-mapper.md) * [联合类型转换](mapper/union-mapper.md) * [输出格式](mapper/out.md) +* [参数校验](mapper/validate.md) + ## 注解类使用 diff --git a/docs/zh/mapper/validate.md b/docs/zh/mapper/validate.md new file mode 100644 index 0000000..84aab43 --- /dev/null +++ b/docs/zh/mapper/validate.md @@ -0,0 +1,61 @@ +## 参数校验 + +### 使用 validate 方法进行参数校验 + +`Serialize` 类提供了一个 `validate` 方法,当通过 `from` 方法创建对象时,该方法会被自动调用。我们可以在 `validate` 方法中实现自定义的数据验证逻辑。 + +下面是一个使用示例: + +```php +use Astral\Serialize\Serialize; + +class TestConstructValidationFromSerialize extends Serialize +{ + public string $type_string; + + /** + * 数据验证方法 + * 在对象通过 from 方法创建后自动调用 + */ + public function validate(): void + { + // 验证 type_string 属性的值 + if ($this->type_string !== '123') { + throw new Exception('type_string 必须等于 123'); + } + + // 可以修改属性值 + $this->type_string = '234'; + } +} +``` + +### 在构造函数中进行参数校验 + +当直接使用 `__construct` 方法创建对象时,可以在构造函数中实现参数验证逻辑。但需要注意的是,这种方式只能访问构造函数中定义的属性,无法访问其他属性。 + +```php +use Astral\Serialize\Serialize; + +class TestConstructFromSerialize extends Serialize +{ + // 注意:这个属性在构造函数中无法访问 + // 如果需要验证此属性,请使用 validate 方法 + public string $not_validate_string; + + /** + * 构造函数中的参数验证 + * @param string $type_string 输入的字符串参数 + */ + public function __construct( + public string $type_string, + ) { + // 验证传入的参数 + if ($this->type_string !== '123') { + throw new Exception('type_string 必须等于 123'); + } + + // 修改属性值 + $this->type_string = '234'; + } +} \ No newline at end of file diff --git a/src/Casts/InputValue/InputValueOnlyBaseTypeCast.php b/src/Casts/InputValue/InputValueOnlyBaseTypeCast.php new file mode 100755 index 0000000..accee6f --- /dev/null +++ b/src/Casts/InputValue/InputValueOnlyBaseTypeCast.php @@ -0,0 +1,29 @@ +isNullable() === false && count($collection->getTypes()) == 1 ; + } + + public function resolve(mixed $value, DataCollection $collection, InputValueContext $context): mixed + { + return match ($collection->getTypes()[0]->kind) { + TypeKindEnum::INT => (int)$value, + TypeKindEnum::FLOAT => (float)$value, + TypeKindEnum::STRING => (string)$value, + TypeKindEnum::BOOLEAN => (bool)$value, + default => $value, + }; + } +} diff --git a/src/Resolvers/InputResolver.php b/src/Resolvers/InputResolver.php index 678ed63..6f9ec36 100644 --- a/src/Resolvers/InputResolver.php +++ b/src/Resolvers/InputResolver.php @@ -26,7 +26,7 @@ public function __construct( /** * @throws ReflectionException */ - public function resolve(ChooseSerializeContext $chooseContext, GroupDataCollection $groupCollection, array $payload) + public function resolve(ChooseSerializeContext $chooseContext, GroupDataCollection $groupCollection, array $payload): object { $reflectionClass = $this->reflectionClassInstanceManager->get($groupCollection->getClassName()); $object = $reflectionClass->newInstanceWithoutConstructor(); @@ -72,6 +72,11 @@ classInstance: $object, $this->inputConstructCast->resolve($groupCollection->getConstructProperties(), $object, $constructInputs); } + // validate execution + if(method_exists($object,'validate')){ + $object->validate(); + } + return $object; } diff --git a/src/Serialize.php b/src/Serialize.php index 30bfca9..4ac6456 100644 --- a/src/Serialize.php +++ b/src/Serialize.php @@ -32,7 +32,6 @@ public function setContext(SerializeContext $context): static public static function setGroups(array|string $groups): SerializeContext { - /** @var SerializeContext $serializeContext */ $serializeContext = ContextFactory::build(static::class); return $serializeContext->setGroups((array)$groups); } @@ -84,6 +83,11 @@ public static function faker(): static return $instance; } + public function validate(): void + { + + } + public function __debugInfo() { $res = get_object_vars($this); diff --git a/src/Support/Config/ConfigManager.php b/src/Support/Config/ConfigManager.php index 310d26f..db1bfd8 100644 --- a/src/Support/Config/ConfigManager.php +++ b/src/Support/Config/ConfigManager.php @@ -7,6 +7,7 @@ use Astral\Serialize\Casts\InputValue\InputObjectBestMatchChildCast; use Astral\Serialize\Casts\InputValue\InputValueEnumCast; use Astral\Serialize\Casts\InputValue\InputValueNullCast; +use Astral\Serialize\Casts\InputValue\InputValueOnlyBaseTypeCast; use Astral\Serialize\Casts\Normalizer\ArrayNormalizerCast; use Astral\Serialize\Casts\Normalizer\DateTimeNormalizerCast; use Astral\Serialize\Casts\Normalizer\JsonNormalizerCast; @@ -36,6 +37,7 @@ class ConfigManager InputArraySingleChildCast::class, InputArrayBestMatchChildCast::class, InputValueEnumCast::class, + InputValueOnlyBaseTypeCast::class, ]; /** @var (OutValueCastInterface|string)[] $outputValueCasts */ diff --git a/src/Support/Context/SerializeContext.php b/src/Support/Context/SerializeContext.php index c376bb7..52ab275 100644 --- a/src/Support/Context/SerializeContext.php +++ b/src/Support/Context/SerializeContext.php @@ -20,16 +20,13 @@ use ReflectionProperty; use RuntimeException; -/** - * @template T - */ class SerializeContext { private array $groups = []; private array $responses = []; public function __construct( - /** @var class-string */ + /** @var class-string */ private readonly string $serializeClassName, private readonly ChooseSerializeContext $chooseSerializeContext, private readonly CacheInterface $cache, @@ -208,6 +205,7 @@ className: $type->className, /** * @param mixed ...$payload + * @return object */ public function from(mixed ...$payload): object { @@ -220,7 +218,7 @@ public function from(mixed ...$payload): object $this->chooseSerializeContext->setGroups($this->getGroups()); - /** @var T $object */ + $object = $this->propertyInputValueResolver->resolve($this->chooseSerializeContext, $this->getGroupCollection(), $payloads); if ($object instanceof Serialize && $object->getContext() === null) { @@ -231,7 +229,7 @@ public function from(mixed ...$payload): object } - public function faker() + public function faker(): object { $this->chooseSerializeContext->setGroups($this->getGroups()); return $this->fakerResolver->resolve($this->chooseSerializeContext, $this->getGroupCollection()); diff --git a/src/Support/Factories/ContextFactory.php b/src/Support/Factories/ContextFactory.php index f462f0f..d869f06 100644 --- a/src/Support/Factories/ContextFactory.php +++ b/src/Support/Factories/ContextFactory.php @@ -6,15 +6,9 @@ use Astral\Serialize\Support\Context\ChooseSerializeContext; use Astral\Serialize\Support\Context\SerializeContext; -/** - * @template T - */ + class ContextFactory { - /** - * @param class-string $className - * @return SerializeContext - */ public static function build(string $className): SerializeContext { return (new SerializeContext( diff --git a/tests/Serialize/From/FromSerializeTest.php b/tests/Serialize/From/FromSerializeTest.php index 96b6712..20addb3 100644 --- a/tests/Serialize/From/FromSerializeTest.php +++ b/tests/Serialize/From/FromSerializeTest.php @@ -30,6 +30,34 @@ public function __construct( $this->type_null = $abc; } } + + class TestConstructFromSerialize extends Serialize + { + public function __construct( + public string $type_string, + public object $type_object, + public int $type_int, + public int $type_null, + public float $type_float, + ) { + } + } + + class TestConstructValidationFromSerialize extends Serialize + { + + public string $type_string; + + public function validate():void + { + if($this->type_string !== '123'){ + throw new Exception('type_string is not 123'); + } + + $this->type_string = '234'; + + } + } }); it('test parse Serialize class', function () { @@ -43,7 +71,7 @@ public function __construct( 'type_float' => 0.02, 'withoutType' => 'hhh', ], - type_float:null, + type_float:null, // 'type_float' => 0.02 change to null input_name:null, type_object:null, type_mixed_other: ['abc' => ['bbb' => ['ccc' => 'dddd'],['abc']],'aaa','bbb','ccc',''], @@ -61,3 +89,35 @@ public function __construct( ->and($object->type_mixed_other['abc']['bbb']['ccc'])->toBe('dddd') ->and($object->type_collect_object)->toBeInstanceOf(StdClass::class); }); + +it('test parse construct Serialize class', function () { + + $object = TestConstructFromSerialize::from( + type_string: 123, + type_object: null, + type_int: '123', + type_null: null, + type_float: '0.01', + ); + + expect($object)->toBeInstanceOf(TestConstructFromSerialize::class) + ->and($object->type_string)->toBe('123') + ->and($object->type_object)->toBeInstanceOf(StdClass::class) + ->and($object->type_int)->toBe(123) + ->and($object->type_null)->toBe(0) + ->and($object->type_float)->toBe(0.01); +}); + +it('throws exception when type_string is not 123', function () { + expect(function () { + TestConstructValidationFromSerialize::from(type_string: 111); + })->toThrow(Exception::class, 'type_string is not 123'); +}); + +it('creates object successfully when type_string is 123', function () { + $object = TestConstructValidationFromSerialize::from(type_string: 123); + + expect($object) + ->toBeInstanceOf(TestConstructValidationFromSerialize::class) + ->and($object->type_string)->toBe('234'); +}); \ No newline at end of file diff --git a/tests/Serialize/From/NormalizerFromSerializeTest.php b/tests/Serialize/From/NormalizerFromSerializeTest.php index 0d8f0b0..4ecfcdc 100644 --- a/tests/Serialize/From/NormalizerFromSerializeTest.php +++ b/tests/Serialize/From/NormalizerFromSerializeTest.php @@ -35,7 +35,7 @@ class NormalizerClass extends Serialize $normalizerOne->name_one = 'one name'; $normalizerOne->id_one = 1; - $normalizerTwo = new NormalizerTwo(); + $normalizerTwo = new NormalizerTwo(); $normalizerTwo->name_two = 'two name'; $normalizerTwo->id_two = 2;