5
5
``` php
6
6
<?php
7
7
8
- use Archman\Velcro\Converters\DateTimeConverter ;
8
+ use Archman\Velcro\Converters\DateTimeImmutableConverter ;
9
9
use Archman\Velcro\DataModel;
10
10
use Archman\Velcro\Field;
11
- use Archman\Velcro\Readonly ;
11
+ use Archman\Velcro\RO ;
12
12
13
13
class Foo extends DataModel
14
14
{
15
15
#[Field('field1')]
16
16
public int $val1;
17
17
18
- #[Field('field2'), DateTimeConverter(DateTimeConverter ::ISO_8601)]
19
- public DateTime $val2;
18
+ #[Field('field2'), DateTimeImmutableConverter(DateTimeImmutableConverter ::ISO_8601)]
19
+ public DateTimeImmutable $val2;
20
20
21
- #[Field('field3'), Readonly ]
21
+ #[Field('field3'), RO ]
22
22
public string $val3;
23
+
24
+ #[Field('field4')]
25
+ public readonly string $val4;
23
26
}
24
27
25
28
$foo = new Foo([
26
29
'field1' => 123,
27
30
'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'
29
33
]);
30
34
31
35
assert($foo->val1 === 123);
32
36
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.
35
40
```
36
41
37
42
# 简介
38
- PHP现在有了type hinting, 有typed properties,有了union types,再配合strict types,我们写出的代码相对过去能拥有更佳的类型安全.
39
-
40
- 所以过去一些使用关联数组来组织信息的地方,现在我可能会使用值对象的方式来组织信息.
41
-
42
- 以前一部分类似于这样` doSomething($foo['bar']) ` 的代码
43
+ 这个库帮助你用更少的代码自动将关联数组的值附给对象的属性.
43
44
44
- 现在会写成这样` doSomething($foo->bar) `
45
+ ### 什么场景下我会需要这种东西?
46
+ 当你需要从接口请求参数来创建DTO; 当你需要通过对象的形式来管理配置; 抑或者从外部数据源得到的数据恢复成对象时。
45
47
46
- 这样:
47
- * 我在重构这部分代码时更容易
48
- * 这些类型错误可以更早的暴露出来
49
- * 静态分析器也可以帮助我提前发现一些问题
48
+ 从这些预先定义好的数据恢复时,往往就需要大量无趣的组装操作,通常还伴随着一些前置条件逻辑,比如判断字段是否存在,类型是否需要转换。
50
49
51
- 所以当我真正要做这件事的时候,我就需要把关联数组中的数据一个个地赋给对象的属性,极其枯燥且易错.
52
-
53
- 这种事情应该交给程序来做,于是有了这个小玩意。
50
+ 用代码生成工具是一种减少人力的方法。 而这个库是另一种方法,它让你只需要定义类属性所关联的字段,剩下的组装操作由库来完成。
54
51
55
52
# 要求
56
53
PHP >= 8.0
@@ -107,32 +104,33 @@ class Foo
107
104
### 类型匹配
108
105
尽管内部帮你解决了赋值的问题, 但是它不会帮你匹配类型,更不会自动帮你转换类型, 所以当你的类属性和数据字段的类型不一致时,会抛出异常,因为Velcro的类都是以strict_types模式定义的.
109
106
110
- ## 数据转换器
107
+ ## 数据转换器 (Converter)
111
108
然而你定义的类属性,可能是任意类型. 同时你的数据可能不是来自于程序内部,有可能是http请求/响应体,或者外部配置/存储,异步消息等,双方的类型可能并不匹配.
112
109
113
110
此时,你需要用到数据转换器, 把来源数据转换成其对应属性的类型, 这样你可按照使用上更舒服的方式去定义类的属性.
114
111
115
112
### 使用数据转换器
116
- 当我们从第三方接口获得数据中包含了一个时间戳的字段,比如` $data['time'] ` ,而在我们的代码中需要以` DateTime ` 类型来使用, 与其我们手动的编写转换代码, 我们可以这样定义
113
+ 当我们从第三方接口获得数据中包含了一个时间戳的字段,比如` $data['time'] ` ,而在我们的代码中需要以` DateTimeImmutable ` 类型来使用, 与其我们手动的编写转换代码, 我们可以这样定义
114
+
117
115
``` php
118
116
<?php
119
117
120
- use Archman\Velcro\Converters\DateTimeConverter ;
118
+ use Archman\Velcro\Converters\DateTimeImmutableConverter ;
121
119
use Archman\Velcro\DataModel;
122
120
use Archman\Velcro\Field;
123
121
124
122
class Response extends DataModel
125
123
{
126
124
#[Field('time')]
127
- #[DateTimeConverter(DateTimeConverter ::TIMESTAMP)]
128
- public DateTime $datetime;
125
+ #[DateTimeImmutableConverter(DateTimeImmutableConverter ::TIMESTAMP)]
126
+ public DateTimeImmutable $datetime;
129
127
}
130
128
new Response(['time' => 1609430400]);
131
129
```
132
130
133
- Velcro会先使用` DateTimeConverter ` 帮你把时间戳转换成DateTime类型再赋给对应的属性 .
131
+ Velcro会先使用` DateTimeConverter ` 帮你把时间戳转换成 DateTimeImmutable 类型再赋给对应的属性 .
134
132
135
- Velcro中预先定义了少量的转换器 ,用来应对不同的场景.
133
+ Velcro中预先定义了一些的转换器 ,用来应对不同的场景.
136
134
137
135
### 嵌套DataModel
138
136
你可以使用` ModelConverter ` 和` ModelListConverter ` 这两个转换器,实现数据模型的嵌套.
@@ -206,20 +204,20 @@ assert($info->school->name === 'xxx');
206
204
** 通过实现ConverterInterface接口,你可以实现自己的数据转换器**
207
205
208
206
## 只读属性
209
- 有些情况下,你可能需要你的模型是不可变的, 例如你有一个全局配置的模型对象,你不希望使用方更改配置的值,你可以对类或属性标记Readonly来达到目的
207
+ 有些情况下,你可能需要你的模型是不可变的, 例如你有一个全局配置的模型对象,你不希望使用方更改配置的值,你可以对类或属性标记RO来达到目的
208
+
210
209
``` php
211
210
<?php
212
211
213
212
use Archman\Velcro\DataModel;
214
213
use Archman\Velcro\Exceptions\ReadonlyException;
215
214
use Archman\Velcro\Field;
216
- use Archman\Velcro\Readonly ;
215
+ use Archman\Velcro\RO ;
217
216
218
- // 将属性标记Readonly, 使得指定属性变为只读
217
+ // 将属性添加RO注解, 会使该属性变为只读
219
218
class ConfigA extends DataModel
220
219
{
221
- #[Field('conf1')]
222
- #[Readonly]
220
+ #[Field('conf1'), RO]
223
221
public string $config1;
224
222
225
223
#[Field('conf2')]
@@ -239,8 +237,8 @@ $c->config2 = 222;
239
237
assert($c->config2 === 222);
240
238
241
239
242
- // 将类标记为Readonly, 其中所有标记了Field的属性都会变为只读
243
- #[Readonly ]
240
+ // 当将类添加RO注解, 等同于将其中所有标记了Field都会变为只读
241
+ #[RO ]
244
242
class ConfigB extends DataModel
245
243
{
246
244
#[Field('conf1')]
@@ -271,6 +269,27 @@ try {
271
269
$c->config3 = 'xxx'; // 没有标记Field, 不会抛出异常
272
270
```
273
271
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
+
274
293
## 私有属性
275
294
在上面的例子中,都是使用public属性进行演示. 但实际上Velcro同样能赋值给protected和private属性
276
295
0 commit comments