Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion benchmarks/SerializeBench.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down
1 change: 1 addition & 0 deletions docs/en/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions docs/en/mapper/out.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
61 changes: 61 additions & 0 deletions docs/en/mapper/validate.md
Original file line number Diff line number Diff line change
@@ -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';
}
}
2 changes: 2 additions & 0 deletions docs/zh/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
* [数组对象转换](mapper/array-mapper.md)
* [联合类型转换](mapper/union-mapper.md)
* [输出格式](mapper/out.md)
* [参数校验](mapper/validate.md)


## 注解类使用

Expand Down
61 changes: 61 additions & 0 deletions docs/zh/mapper/validate.md
Original file line number Diff line number Diff line change
@@ -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';
}
}
29 changes: 29 additions & 0 deletions src/Casts/InputValue/InputValueOnlyBaseTypeCast.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

declare(strict_types=1);

namespace Astral\Serialize\Casts\InputValue;

use Astral\Serialize\Contracts\Attribute\InputValueCastInterface;
use Astral\Serialize\Enums\TypeKindEnum;
use Astral\Serialize\Support\Collections\DataCollection;
use Astral\Serialize\Support\Context\InputValueContext;

class InputValueOnlyBaseTypeCast implements InputValueCastInterface
{
public function match(mixed $value, DataCollection $collection, InputValueContext $context): bool
{
return $value !== null && $collection->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,
};
}
}
7 changes: 6 additions & 1 deletion src/Resolvers/InputResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -72,6 +72,11 @@ classInstance: $object,
$this->inputConstructCast->resolve($groupCollection->getConstructProperties(), $object, $constructInputs);
}

// validate execution
if(method_exists($object,'validate')){
$object->validate();
}

return $object;

}
Expand Down
6 changes: 5 additions & 1 deletion src/Serialize.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ public function setContext(SerializeContext $context): static

public static function setGroups(array|string $groups): SerializeContext
{
/** @var SerializeContext<static> $serializeContext */
$serializeContext = ContextFactory::build(static::class);
return $serializeContext->setGroups((array)$groups);
}
Expand Down Expand Up @@ -84,6 +83,11 @@ public static function faker(): static
return $instance;
}

public function validate(): void
{

}

public function __debugInfo()
{
$res = get_object_vars($this);
Expand Down
2 changes: 2 additions & 0 deletions src/Support/Config/ConfigManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -36,6 +37,7 @@ class ConfigManager
InputArraySingleChildCast::class,
InputArrayBestMatchChildCast::class,
InputValueEnumCast::class,
InputValueOnlyBaseTypeCast::class,
];

/** @var (OutValueCastInterface|string)[] $outputValueCasts */
Expand Down
10 changes: 4 additions & 6 deletions src/Support/Context/SerializeContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,13 @@
use ReflectionProperty;
use RuntimeException;

/**
* @template T
*/
class SerializeContext
{
private array $groups = [];
private array $responses = [];

public function __construct(
/** @var class-string<T> */
/** @var class-string */
private readonly string $serializeClassName,
private readonly ChooseSerializeContext $chooseSerializeContext,
private readonly CacheInterface $cache,
Expand Down Expand Up @@ -208,6 +205,7 @@ className: $type->className,

/**
* @param mixed ...$payload
* @return object
*/
public function from(mixed ...$payload): object
{
Expand All @@ -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) {
Expand All @@ -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());
Expand Down
8 changes: 1 addition & 7 deletions src/Support/Factories/ContextFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,9 @@
use Astral\Serialize\Support\Context\ChooseSerializeContext;
use Astral\Serialize\Support\Context\SerializeContext;

/**
* @template T
*/

class ContextFactory
{
/**
* @param class-string<T> $className
* @return SerializeContext<T>
*/
public static function build(string $className): SerializeContext
{
return (new SerializeContext(
Expand Down
62 changes: 61 additions & 1 deletion tests/Serialize/From/FromSerializeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 () {
Expand All @@ -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',''],
Expand All @@ -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');
});
2 changes: 1 addition & 1 deletion tests/Serialize/From/NormalizerFromSerializeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down