Skip to content

Commit e9064b5

Browse files
committed
[feat] ArrayOf
1.新增 ArrayOf 標註支援陣列屬性的自動實例化功能。 2.調整 Relaxed、Expose 標註廢棄時程從 v2.2.0 延後至 v2.3.0。 3.更新 README 文檔說明新功能使用方式和完整範例。
1 parent 8590a03 commit e9064b5

File tree

3 files changed

+126
-8
lines changed

3 files changed

+126
-8
lines changed

CHANGELOG

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,40 @@
11
# CHANGELOG
22

3+
## [v2.2.0] - 2025-07-31
4+
5+
### 🎉 新功能
6+
- **新增 `#[ArrayOf]` 標註**:
7+
- 支援陣列屬性的自動實例化
8+
- 可指定陣列元素的類別類型
9+
- 自動將陣列數據轉換為指定類別的實例
10+
- 提供錯誤驗證,確保類別名稱不為空
11+
12+
### 📝 更新
13+
- **調整廢棄時程**:
14+
- `#[Relaxed]` - 廢棄時程延後至 v2.3.0
15+
- `#[Expose]` - 廢棄時程延後至 v2.3.0
16+
17+
### 📚 範例
18+
```php
19+
#[DataTransferObject]
20+
class UserListDTO extends ImmutableBase
21+
{
22+
#[ArrayOf(UserDTO::class)]
23+
public readonly array $users;
24+
}
25+
26+
// 使用方式
27+
$userList = new UserListDTO([
28+
'users' => [
29+
['name' => 'Alice', 'age' => 30],
30+
['name' => 'Bob', 'age' => 25]
31+
]
32+
]);
33+
// 自動將每個陣列元素轉換為 UserDTO 實例
34+
```
35+
36+
---
37+
338
## [v2.1.0] - 2025-08-01
439

540
### 🎉 新功能
@@ -18,8 +53,8 @@
1853
- 改進型別驗證錯誤訊息
1954

2055
### 🗑️ 即將棄用標註
21-
- `#[Relaxed]` - 標記為 @deprecated v2.2.0
22-
- `#[Expose]` - 標記為 @deprecated v2.2.0
56+
- `#[Relaxed]` - 標記為 @deprecated v2.3.0
57+
- `#[Expose]` - 標記為 @deprecated v2.3.0
2358

2459
### 📚 範例
2560
```php

README.md

Lines changed: 63 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
-**支援 `with([...])` 複製模式,包含嵌套物件更新**
1616
-**自動 `toArray()``jsonSerialize()`**
1717
-**架構模式標註:`#[DataTransferObject]``#[ValueObject]``#[Entity]`**
18+
-**陣列自動實例化:`#[ArrayOf]`**
1819
-**屬性標註系統:`#[Expose]``#[Reason]``#[Relaxed]`**
1920
-**屬性訪問控制與設計原因強制說明**
2021

@@ -147,7 +148,48 @@ class User extends ImmutableBase
147148

148149
> ⚠️ **注意**:以下標註即將於 v2.2.0 棄用,建議使用架構模式標註。
149150
150-
### `#[Expose]` - 輸出控制 (@deprecated v2.2.0)
151+
````
152+
153+
### `#[ArrayOf]` - 陣列自動實例化
154+
155+
指定陣列屬性中每個元素的類別,自動將陣列數據轉換為指定類別的實例:
156+
157+
```php
158+
use ReallifeKip\ImmutableBase\ArrayOf;
159+
use ReallifeKip\ImmutableBase\DataTransferObject;
160+
161+
#[DataTransferObject]
162+
class UserListDTO extends ImmutableBase
163+
{
164+
#[ArrayOf(UserDTO::class)]
165+
public readonly array $users;
166+
167+
#[ArrayOf(TagDTO::class)]
168+
public readonly array $tags;
169+
}
170+
171+
// 使用方式
172+
$userList = new UserListDTO([
173+
'users' => [
174+
['name' => 'Alice', 'age' => 30],
175+
['name' => 'Bob', 'age' => 25]
176+
],
177+
'tags' => [
178+
['name' => 'developer'],
179+
['name' => 'senior']
180+
]
181+
]);
182+
183+
// users 和 tags 陣列中的每個元素都會自動轉換為對應的 DTO 實例
184+
````
185+
186+
---
187+
188+
## 屬性標註系統(將於 v2.3.0 棄用)
189+
190+
> ⚠️ **注意**:以下標註將在 v2.3.0 標記為 deprecated,建議使用架構模式標註。
191+
192+
### `#[Expose]` - 輸出控制 (將於 v2.3.0 棄用)
151193

152194
標記可被 `toArray()``jsonSerialize()` 輸出的屬性:
153195

@@ -161,7 +203,7 @@ class UserDTO extends ImmutableBase
161203
}
162204
```
163205

164-
### `#[Reason]` - 設計原因說明 (@deprecated v2.2.0)
206+
### `#[Reason]` - 設計原因說明 (將於 v2.3.0 棄用)
165207

166208
當屬性不是 `private` 時,必須使用此標註說明設計原因:
167209

@@ -177,7 +219,7 @@ class UserDTO extends ImmutableBase
177219
}
178220
```
179221

180-
### `#[Relaxed]` - 鬆散模式 (@deprecated v2.2.0)
222+
### `#[Relaxed]` - 鬆散模式 (將於 v2.3.0 棄用)
181223

182224
標記在 class 上,允許不強制要求 `#[Reason]` 標註:
183225

@@ -264,7 +306,8 @@ $user = $user->with([
264306
3. **不支援 constructor injection**:請以 `$data` array 傳入
265307
4. **屬性標註規則**
266308
- **推薦**:使用架構模式標註 `#[DataTransferObject]``#[ValueObject]``#[Entity]`
267-
- **已棄用**`#[Expose]``#[Reason]``#[Relaxed]` 標註(v2.2.0 將移除)
309+
- **陣列處理**:使用 `#[ArrayOf(ClassName::class)]` 進行陣列自動實例化
310+
- **將棄用**`#[Expose]``#[Reason]``#[Relaxed]` 標註(v2.3.0 將棄用)
268311
- DataTransferObject 要求所有屬性為 `public readonly`
269312
- ValueObject 和 Entity 要求所有屬性為 `private`
270313

@@ -278,6 +321,7 @@ $user = $user->with([
278321
use ReallifeKip\ImmutableBase\ImmutableBase;
279322
use ReallifeKip\ImmutableBase\DataTransferObject;
280323
use ReallifeKip\ImmutableBase\ValueObject;
324+
use ReallifeKip\ImmutableBase\ArrayOf;
281325

282326
#[DataTransferObject]
283327
class AddressDTO extends ImmutableBase
@@ -286,11 +330,21 @@ class AddressDTO extends ImmutableBase
286330
public readonly string $zipCode;
287331
}
288332

333+
#[DataTransferObject]
334+
class TagDTO extends ImmutableBase
335+
{
336+
public readonly string $name;
337+
public readonly ?string $color = null;
338+
}
339+
289340
#[DataTransferObject]
290341
class ProfileDTO extends ImmutableBase
291342
{
292343
public readonly AddressDTO $address;
293344
public readonly ?string $phone = null;
345+
346+
#[ArrayOf(TagDTO::class)]
347+
public readonly array $tags;
294348
}
295349

296350
#[ValueObject]
@@ -323,7 +377,11 @@ $user = new UserDTO([
323377
'city' => '台北市',
324378
'zipCode' => '10001'
325379
],
326-
'phone' => '0912-345-678'
380+
'phone' => '0912-345-678',
381+
'tags' => [
382+
['name' => 'developer', 'color' => 'blue'],
383+
['name' => 'senior', 'color' => 'gold']
384+
]
327385
]
328386
]);
329387

src/ImmutableBase.php

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ final class Expose
3636
*
3737
* 所有屬性必須為 public readonly
3838
*/
39+
#[\Attribute(\Attribute::TARGET_CLASS)]
3940
final class DataTransferObject
4041
{
4142
}
@@ -45,6 +46,7 @@ final class DataTransferObject
4546
*
4647
* 所有屬性必須為 private
4748
*/
49+
#[\Attribute(\Attribute::TARGET_CLASS)]
4850
final class ValueObject
4951
{
5052
}
@@ -54,10 +56,24 @@ final class ValueObject
5456
*
5557
* 所有屬性必須為 private
5658
*/
59+
#[\Attribute(\Attribute::TARGET_CLASS)]
5760
final class Entity
5861
{
5962
}
6063

64+
#[\Attribute(\Attribute::TARGET_PROPERTY)]
65+
final class ArrayOf
66+
{
67+
public bool $error = false;
68+
public function __construct(
69+
public string $class = '',
70+
) {
71+
if (trim($this->class) === '') {
72+
$this->error = true;
73+
}
74+
}
75+
}
76+
6177
/**
6278
* 設計原因
6379
*
@@ -94,8 +110,17 @@ public function __construct(array $data = [])
94110
$exists = array_key_exists($key, $data);
95111
$nullable = $type->allowsNull();
96112
$hasDefault = $property->hasDefaultValue();
113+
$arrayOf = $property->getAttributes(ArrayOf::class);
114+
$class = null;
115+
if ($arrayOf) {
116+
if ($arrayOf[0]->newInstance()->error) {
117+
throw new Exception("ArrayOf class 不能為空");
118+
}
119+
$class = $arrayOf[0]->getArguments()[0];
120+
}
97121
$value = match(true) {
98122
!$exists && !$nullable => throw new Exception("$key 必須傳入 $type"),
123+
$arrayOf && $class => array_map(fn ($item) => new $class($item), $data[$key]),
99124
!$exists && $nullable && !$hasDefault => null,
100125
!$exists && $nullable && $hasDefault => $property->getDefaultValue(),
101126
$exists => $this->valueDecide($type, $data[$key]),
@@ -142,7 +167,7 @@ private function walkProperties(callable $callback): void
142167
/**
143168
* 更新並返回新的實例
144169
* @param array $data
145-
* @return ImmutableBase
170+
* @return static
146171
*/
147172
final public function with(array $data): static
148173
{

0 commit comments

Comments
 (0)