Skip to content

Commit 804d9ac

Browse files
authored
Merge pull request #1 from hsldymq/compat-8.1
new ver
2 parents 251bd6c + c075ce8 commit 804d9ac

34 files changed

+526
-153
lines changed

.travis.yml

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
language: php
22

3+
os:
4+
- linux
5+
dist: bionic
6+
37
php:
48
- '8.0'
9+
- '8.1.0'
510

611
install: composer install
712

813
script: XDEBUG_MODE=coverage php ./vendor/bin/phpunit --coverage-clover coverage.xml
914

1015
after_success:
11-
- bash <(curl -s https://codecov.io/bash)
16+
- bash <(curl -s https://codecov.io/bash)

CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# v1.1.0 (2021-08-06)
2+
* 增加一个方法: 允许获取原始数组中未分配给属性的字段列表
3+
4+
# v1.0.1 (2021-04-14)
5+
* 修复从关联数组中分配嵌套模型列表时,丢失key的问题

README.md

+54-35
Original file line numberDiff line numberDiff line change
@@ -5,52 +5,49 @@
55
```php
66
<?php
77

8-
use Archman\Velcro\Converters\DateTimeConverter;
8+
use Archman\Velcro\Converters\DateTimeImmutableConverter;
99
use Archman\Velcro\DataModel;
1010
use Archman\Velcro\Field;
11-
use Archman\Velcro\Readonly;
11+
use Archman\Velcro\RO;
1212

1313
class Foo extends DataModel
1414
{
1515
#[Field('field1')]
1616
public int $val1;
1717

18-
#[Field('field2'), DateTimeConverter(DateTimeConverter::ISO_8601)]
19-
public DateTime $val2;
18+
#[Field('field2'), DateTimeImmutableConverter(DateTimeImmutableConverter::ISO_8601)]
19+
public DateTimeImmutable $val2;
2020

21-
#[Field('field3'), Readonly]
21+
#[Field('field3'), RO]
2222
public string $val3;
23+
24+
#[Field('field4')]
25+
public readonly string $val4;
2326
}
2427

2528
$foo = new Foo([
2629
'field1' => 123,
2730
'field2' => '2021-01-01T00:00:00',
28-
'field3' => 'readonly value',
31+
'field3' => 'value for readonly field',
32+
'field4' => 'value for PHP 8.1 readonly field'
2933
]);
3034

3135
assert($foo->val1 === 123);
3236
assert($foo->val2->format('Y-m-d H:i:s') === '2021-01-01 00:00:00');
33-
assert($foo->val3 === 'readonly value');
34-
$foo->val3 = 'new value'; // it will throw an exception
37+
assert($foo->val3 === 'value for readonly field');
38+
assert($foo->val4 === 'value for PHP 8.1 readonly field');
39+
$foo->val3 = 'new value'; // It throws an exception.
3540
```
3641

3742
# 简介
38-
PHP现在有了type hinting, 有typed properties,有了union types,再配合strict types,我们写出的代码相对过去能拥有更佳的类型安全.
39-
40-
所以过去一些使用关联数组来组织信息的地方,现在我可能会使用值对象的方式来组织信息.
41-
42-
以前一部分类似于这样`doSomething($foo['bar'])`的代码
43+
这个库帮助你用更少的代码自动将关联数组的值附给对象的属性.
4344

44-
现在会写成这样`doSomething($foo->bar)`
45+
### 什么场景下我会需要这种东西?
46+
当你需要从接口请求参数来创建DTO; 当你需要通过对象的形式来管理配置; 抑或者从外部数据源得到的数据恢复成对象时。
4547

46-
这样:
47-
* 我在重构这部分代码时更容易
48-
* 这些类型错误可以更早的暴露出来
49-
* 静态分析器也可以帮助我提前发现一些问题
48+
从这些预先定义好的数据恢复时,往往就需要大量无趣的组装操作,通常还伴随着一些前置条件逻辑,比如判断字段是否存在,类型是否需要转换。
5049

51-
所以当我真正要做这件事的时候,我就需要把关联数组中的数据一个个地赋给对象的属性,极其枯燥且易错.
52-
53-
这种事情应该交给程序来做,于是有了这个小玩意。
50+
用代码生成工具是一种减少人力的方法。 而这个库是另一种方法,它让你只需要定义类属性所关联的字段,剩下的组装操作由库来完成。
5451

5552
# 要求
5653
PHP >= 8.0
@@ -107,32 +104,33 @@ class Foo
107104
### 类型匹配
108105
尽管内部帮你解决了赋值的问题, 但是它不会帮你匹配类型,更不会自动帮你转换类型, 所以当你的类属性和数据字段的类型不一致时,会抛出异常,因为Velcro的类都是以strict_types模式定义的.
109106

110-
## 数据转换器
107+
## 数据转换器 (Converter)
111108
然而你定义的类属性,可能是任意类型. 同时你的数据可能不是来自于程序内部,有可能是http请求/响应体,或者外部配置/存储,异步消息等,双方的类型可能并不匹配.
112109

113110
此时,你需要用到数据转换器, 把来源数据转换成其对应属性的类型, 这样你可按照使用上更舒服的方式去定义类的属性.
114111

115112
### 使用数据转换器
116-
当我们从第三方接口获得数据中包含了一个时间戳的字段,比如`$data['time']`,而在我们的代码中需要以`DateTime`类型来使用, 与其我们手动的编写转换代码, 我们可以这样定义
113+
当我们从第三方接口获得数据中包含了一个时间戳的字段,比如`$data['time']`,而在我们的代码中需要以`DateTimeImmutable`类型来使用, 与其我们手动的编写转换代码, 我们可以这样定义
114+
117115
```php
118116
<?php
119117

120-
use Archman\Velcro\Converters\DateTimeConverter;
118+
use Archman\Velcro\Converters\DateTimeImmutableConverter;
121119
use Archman\Velcro\DataModel;
122120
use Archman\Velcro\Field;
123121

124122
class Response extends DataModel
125123
{
126124
#[Field('time')]
127-
#[DateTimeConverter(DateTimeConverter::TIMESTAMP)]
128-
public DateTime $datetime;
125+
#[DateTimeImmutableConverter(DateTimeImmutableConverter::TIMESTAMP)]
126+
public DateTimeImmutable $datetime;
129127
}
130128
new Response(['time' => 1609430400]);
131129
```
132130

133-
Velcro会先使用`DateTimeConverter`帮你把时间戳转换成DateTime类型再赋给对应的属性.
131+
Velcro会先使用`DateTimeConverter`帮你把时间戳转换成 DateTimeImmutable 类型再赋给对应的属性.
134132

135-
Velcro中预先定义了少量的转换器,用来应对不同的场景.
133+
Velcro中预先定义了一些的转换器,用来应对不同的场景.
136134

137135
### 嵌套DataModel
138136
你可以使用`ModelConverter``ModelListConverter`这两个转换器,实现数据模型的嵌套.
@@ -206,20 +204,20 @@ assert($info->school->name === 'xxx');
206204
**通过实现ConverterInterface接口,你可以实现自己的数据转换器**
207205

208206
## 只读属性
209-
有些情况下,你可能需要你的模型是不可变的, 例如你有一个全局配置的模型对象,你不希望使用方更改配置的值,你可以对类或属性标记Readonly来达到目的
207+
有些情况下,你可能需要你的模型是不可变的, 例如你有一个全局配置的模型对象,你不希望使用方更改配置的值,你可以对类或属性标记RO来达到目的
208+
210209
```php
211210
<?php
212211

213212
use Archman\Velcro\DataModel;
214213
use Archman\Velcro\Exceptions\ReadonlyException;
215214
use Archman\Velcro\Field;
216-
use Archman\Velcro\Readonly;
215+
use Archman\Velcro\RO;
217216

218-
// 将属性标记Readonly, 使得指定属性变为只读
217+
// 将属性添加RO注解, 会使该属性变为只读
219218
class ConfigA extends DataModel
220219
{
221-
#[Field('conf1')]
222-
#[Readonly]
220+
#[Field('conf1'), RO]
223221
public string $config1;
224222

225223
#[Field('conf2')]
@@ -239,8 +237,8 @@ $c->config2 = 222;
239237
assert($c->config2 === 222);
240238

241239

242-
// 将类标记为Readonly, 其中所有标记了Field的属性都会变为只读
243-
#[Readonly]
240+
// 当将类添加RO注解, 等同于将其中所有标记了Field都会变为只读
241+
#[RO]
244242
class ConfigB extends DataModel
245243
{
246244
#[Field('conf1')]
@@ -271,6 +269,27 @@ try {
271269
$c->config3 = 'xxx'; // 没有标记Field, 不会抛出异常
272270
```
273271

272+
因为PHP 8.1开始增加了readonly关键字,所以当你的环境使用8.1以及之后的版本,你可以抛弃RO直接使用readonly关键来标记只读属性来达到相同的目的
273+
```php
274+
<?php
275+
276+
use Archman\Velcro\DataModel;
277+
use Archman\Velcro\Exceptions\ReadonlyException;
278+
use Archman\Velcro\Field;
279+
use Archman\Velcro\RO;
280+
281+
// 将属性添加RO注解, 会使该属性变为只读
282+
class ConfigX extends DataModel
283+
{
284+
#[Field('conf1')] // 它跟config2有相同的效果
285+
public readonly string $config1;
286+
287+
#[Field('conf2'), RO]
288+
public int $config2;
289+
}
290+
291+
```
292+
274293
## 私有属性
275294
在上面的例子中,都是使用public属性进行演示. 但实际上Velcro同样能赋值给protected和private属性
276295

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Archman\Velcro\Converters;
6+
7+
use Archman\Velcro\Property;
8+
use Attribute;
9+
10+
/**
11+
* 这个Converter用于从整型和字符串恢复为回退枚举(backed enumerations).
12+
*
13+
* @see https://www.php.net/manual/en/language.enumerations.backed.php
14+
*/
15+
#[Attribute]
16+
class BackedEnumConverter implements ConverterInterface
17+
{
18+
private Property $boundProperty;
19+
private \ReflectionEnum $reflectionType;
20+
21+
public function __construct(private \BackedEnum|null $default = null)
22+
{
23+
}
24+
25+
public function bindToProperty(Property $property)
26+
{
27+
$this->reflectionType = new \ReflectionEnum($property->getType()->getName());
28+
if (!$this->reflectionType->isBacked()) {
29+
throw new \InvalidArgumentException('property is not a backed enum');
30+
}
31+
32+
$this->boundProperty = $property;
33+
}
34+
35+
public function convert(mixed $fieldValue): \BackedEnum
36+
{
37+
$method = $this->reflectionType->getMethod('tryFrom');
38+
$e = $method->invoke(null, $fieldValue);
39+
if (!$e) {
40+
if ($this->default === null) {
41+
throw new \InvalidArgumentException('can not recover backed enum from value');
42+
}
43+
$e = $this->default;
44+
}
45+
46+
return $e;
47+
}
48+
}

src/Converters/BoolConverter.php

+8-1
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,18 @@
1212
#[Attribute]
1313
class BoolConverter implements ConverterInterface
1414
{
15+
private Property $boundProperty;
16+
1517
public function __construct(private array $expectTypes = [])
1618
{
1719
}
1820

19-
public function convert(mixed $fieldValue, Property $property): bool
21+
public function bindToProperty(Property $property)
22+
{
23+
$this->boundProperty = $property;
24+
}
25+
26+
public function convert(mixed $fieldValue): bool
2027
{
2128
if (is_bool($fieldValue)) {
2229
return $fieldValue;

src/Converters/ConverterInterface.php

+3-1
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,7 @@
88

99
interface ConverterInterface
1010
{
11-
public function convert(mixed $fieldValue, Property $property): mixed;
11+
public function bindToProperty(Property $property);
12+
13+
public function convert(mixed $fieldValue): mixed;
1214
}

src/Converters/DateTimeConverter.php

-34
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Archman\Velcro\Converters;
6+
7+
use Archman\Velcro\Property;
8+
use Attribute;
9+
10+
#[Attribute]
11+
class DateTimeImmutableConverter implements ConverterInterface
12+
{
13+
/** @var int ISO-8601 格式 */
14+
const ISO_8601 = 0;
15+
16+
/** @var int 时间戳 */
17+
const TIMESTAMP = 1;
18+
19+
/** @var int 毫秒时间戳 */
20+
const TIMESTAMP_MS = 2;
21+
22+
private Property $boundProperty;
23+
24+
public function __construct(private int $type)
25+
{
26+
}
27+
28+
public function bindToProperty(Property $property)
29+
{
30+
$this->boundProperty = $property;
31+
}
32+
33+
public function convert(mixed $fieldValue): \DateTimeImmutable
34+
{
35+
return match ($this->type) {
36+
self::TIMESTAMP => new \DateTimeImmutable("@{$fieldValue}"),
37+
self::TIMESTAMP_MS => new \DateTimeImmutable("@".($fieldValue / 1000)),
38+
default => new \DateTimeImmutable($fieldValue),
39+
};
40+
}
41+
}

src/Converters/FloatConverter.php

+8-1
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,18 @@
1212
#[Attribute]
1313
class FloatConverter implements ConverterInterface
1414
{
15+
private Property $boundProperty;
16+
1517
public function __construct(private array $expectTypes = [])
1618
{
1719
}
1820

19-
public function convert(mixed $fieldValue, Property $property): float
21+
public function bindToProperty(Property $property)
22+
{
23+
$this->boundProperty = $property;
24+
}
25+
26+
public function convert(mixed $fieldValue): float
2027
{
2128
if (is_float($fieldValue)) {
2229
return $fieldValue;

0 commit comments

Comments
 (0)