Skip to content

Commit 75cee25

Browse files
committed
[doc/feat] ArrayOf/ib-writer
1. ArrayOf 1-1. Added support for native-typed arrays: Native::string, Native::int, Native::float, Native::bool, validating PHP scalar values without requiring SingleValueObject. 1-2. Documentation updated to clarify that Native enums are valid targets for ArrayOf. 2. Writer / ib-writer 2-1. Updated class-level descriptions and usage examples to include TypeScript output support. 2-2. TypeScript (.ts) generation now produces: declare namespace blocks, DTO/VO interfaces, SVO type aliases, and enum/union types for referenced PHP enums. 3. InvalidArrayOfItemException 3-1. Fixed @param count and names to match constructor signature. 3-2. Improved message clarity, distinguishing ImmutableBase class resolution failures from primitive type mismatches. 4. InvalidPropertyTypeException 4-1. Added standalone null to the forbidden property type list. 4-2. Updated message to list allowed types and provide guidance for nullable declarations (?Type or Type|null). 5. InvalidArrayOfTargetException 5-1. Documentation updated to indicate Native can be used as a valid ArrayOf target type. 6. ImmutableBase 6-1. scanNamedType(): clarified that null passes through as a builtin type. 6-2. arrayOfInitialize(): removed phantom @param $propertyName.
1 parent 4e3cf17 commit 75cee25

33 files changed

Lines changed: 547 additions & 97 deletions

CHANGELOG.md

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,22 @@
11
# CHANGELOG
22

3-
## [4.1.1] - 2025-03-07
3+
## [v4.2.0] - 2026-03-12
4+
5+
### Added
6+
7+
- **`Native` enum — Primitive typed arrays via `#[ArrayOf]`.** `#[ArrayOf]` now accepts `Native::string`, `Native::int`, `Native::float`, and `Native::bool` to declare arrays of validated PHP scalar values without wrapping them in a SingleValueObject.
8+
- **`ib-writer` TypeScript output.** `vendor/bin/ib-writer` now supports `.ts` generation, producing `declare namespace` blocks with interfaces for DTO/VO, type aliases for SVO, and enum/union types for referenced PHP enums.
9+
10+
### Changed
11+
12+
- **Standalone `null` property type is now forbidden.** Declaring a property typed as `null` alone throws `InvalidPropertyTypeException` at scan time. Use `?Type` or `Type|null` for nullable properties.
13+
- **`InvalidPropertyTypeException` message updated.** Now explicitly lists allowed types and provides nullable usage guidance.
14+
- **`InvalidArrayOfItemException` message updated.** Now distinguishes between ImmutableBase class resolution failures and primitive type mismatches.
15+
16+
## [v4.1.1] - 2025-03-07
417

518
### Fixed
19+
620
#### ib-writer
721
- Property tables now include a `default` column displaying default
822
values from `#[Defaults]` attributes and `defaultValues()` overrides

README.md

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ readonly class Order extends DataTransferObject
3838
public string $date;
3939
public string $time;
4040
}
41-
Order::fromArray($data); // $data can be an array or JSON
41+
Order::fromArray($data); // $data must be an array (use fromJson() for JSON strings)
4242

4343
// 🫤 The conventional approach requires writing constructors manually, cannot directly accept external array or JSON data for construction.
4444
class Order extends DataTransferObject
@@ -115,7 +115,7 @@ SomeException: {error message}
115115

116116
### 📃 Documentation as Code, Code as Documentation
117117

118-
🥳 ImmutableBase can scan all subclasses in your project via `vendor/bin/ib-writer`, generating Mermaid class diagrams and Markdown property tables to keep documentation in sync with code.
118+
🥳 ImmutableBase can scan all subclasses in your project via `vendor/bin/ib-writer`, generating Mermaid class diagrams, Markdown property tables, and TypeScript declarations to keep documentation in sync with code.
119119

120120
🫤 The conventional approach cannot guarantee consistency between code and documentation.
121121

@@ -298,7 +298,7 @@ readonly class ValidAge extends SingleValueObject
298298
```php
299299
$age = ValidAge::from(18);
300300

301-
echo $age; // 18 (via __toString, only available when $value is a string)
301+
echo $age; // 18 (via __toString, string-casts $value)
302302
echo $age(); // 18 (via __invoke)
303303
echo $age->value; // 18
304304
```
@@ -485,16 +485,32 @@ CreateUserDTO::fromArray(['name' => 'Kip']); // role = 'member'
485485

486486
### `#[ArrayOf]` - Typed Array
487487

488-
Marks an array property as a typed collection of ImmutableBase instances. Each element is automatically instantiated from arrays, JSON strings, or pre-built objects. The target class must be a subclass of DTO, VO, or SVO.
488+
Marks an array property as a typed collection of ImmutableBase instances or primitive scalar values. Each element is automatically validated or instantiated. The target must be a subclass of DTO, VO, or SVO, or a `Native` enum case for scalar arrays.
489+
490+
**Primitive scalar arrays** can be declared using `Native` enum cases instead of a class name:
491+
492+
| Case | PHP type |
493+
| ---------------- | -------- |
494+
| `Native::string` | `string` |
495+
| `Native::int` | `int` |
496+
| `Native::float` | `float` |
497+
| `Native::bool` | `bool` |
489498

490499
```php
491500
use ReallifeKip\ImmutableBase\Attributes\ArrayOf;
492501

493502
readonly class SignUpUsersDTO extends DataTransferObject
494503
{
504+
505+
// ImmutableBase subclass
495506
#[ArrayOf(User::class)]
496507
public array $users;
497-
public int $userCount;
508+
509+
// Primitive scalar array
510+
#[ArrayOf(Native::string)]
511+
public array $tags;
512+
#[ArrayOf(Native::int)]
513+
public array $scores;
498514
}
499515
```
500516

@@ -637,7 +653,7 @@ vendor/bin/ib-cacher --clear
637653

638654
### `writer` - Documentation Generator
639655

640-
Generates documentation for all ImmutableBase subclasses in the project. Supports Mermaid class diagrams and Markdown property tables.
656+
Generates documentation for all ImmutableBase subclasses in the project. Supports Mermaid class diagrams, Markdown property tables, and TypeScript declarations.
641657

642658
```bash
643659
vendor/bin/ib-writer
@@ -720,7 +736,7 @@ This section is provided for v3 migration reference only.
720736
## Notes
721737

722738
1. All subclass properties must be public. Since ImmutableBase is declared as a readonly class, the entire inheritance chain must also be readonly at the PHP language level.
723-
2. Forbidden property types: `iterable`, `object`, non-ImmutableBase/non-Enum classes such as `DateTime`, `Closure`.
739+
2. Forbidden property types: `null`, `iterable`, `object`, non-ImmutableBase/non-Enum classes such as `DateTime`, `Closure`.
724740
3. Enum properties accept case names (`"HIGH"`) or backed values (`3`). The resolved property value is always an Enum instance.
725741
4. `mixed` type is supported, but values will not be validated.
726742

README_TW.md

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ readonly class Order extends DataTransferObject
3838
public string $date;
3939
public string $time;
4040
}
41-
Order::fromArray($data); // $data 可以是陣列、JSON
41+
Order::fromArray($data); // $data 必須是陣列(JSON 字串請改用 fromJson())
4242

4343
// 🫤 一般常見做法需要重複撰寫建構子,也常因順序不正確而無法建構,且無法直接使用外部傳入資料進行建構。
4444
class Order extends DataTransferObject
@@ -115,7 +115,7 @@ SomeException: {錯誤訊息}
115115

116116
### 📃文件即代碼,代碼即文件
117117

118-
🥳 ImmutableBase 可以透過 `vendor/bin/ib-writer` 對專案進行 ImmutableBase 子類物件掃描,力求避免文件與代碼不一致、需要花費額外人力的窘境,快速產出 Mermaid、Markdown 等技術文件
118+
🥳 ImmutableBase 可以透過 `vendor/bin/ib-writer` 對專案進行 ImmutableBase 子類物件掃描,力求避免文件與代碼不一致、需要花費額外人力的窘境,快速產出 Mermaid 類別圖、Markdown 屬性表及 TypeScript 型別宣告等技術文件
119119

120120
🫤 一般常見做法無法保障代碼與文件一致。
121121

@@ -298,7 +298,7 @@ readonly class ValidAge extends SingleValueObject
298298
```php
299299
$age = ValidAge::from(18);
300300

301-
echo $age; // 18(透過 __toString, $value 為字串時可用
301+
echo $age; // 18(透過 __toString,會將 $value 轉為字串
302302
echo $age(); // 18(透過 __invoke)
303303
echo $age->value; // 18
304304
```
@@ -485,16 +485,31 @@ CreateUserDTO::fromArray(['name' => 'Kip']); // role = 'member'
485485

486486
### `#[ArrayOf]` - 型別陣列
487487

488-
將陣列屬性標記為 ImmutableBase 實例的型別集合。每個元素會自動從陣列、JSON 字串或已建構物件進行實例化,目標類必須是 DTO、VO 或 SVO 的子類。
488+
將陣列屬性標記為 ImmutableBase 實例或純量值的型別集合。每個元素會自動驗證或實例化。目標必須是 DTO、VO 或 SVO 的子類,或純量陣列可使用 `Native` enum case。
489+
490+
**純量型別陣列**可使用 `Native` enum case 取代類別名稱:
491+
492+
| Case | PHP 型別 |
493+
| ---------------- | -------- |
494+
| `Native::string` | `string` |
495+
| `Native::int` | `int` |
496+
| `Native::float` | `float` |
497+
| `Native::bool` | `bool` |
489498

490499
```php
491500
use ReallifeKip\ImmutableBase\Attributes\ArrayOf;
492501

493502
readonly class SignUpUsersDTO extends DataTransferObject
494503
{
504+
// ImmutableBase 子類
495505
#[ArrayOf(User::class)]
496506
public array $users;
497-
public int $userCount;
507+
508+
// 純量型別陣列
509+
#[ArrayOf(Native::string)]
510+
public array $tags;
511+
#[ArrayOf(Native::int)]
512+
public array $scores;
498513
}
499514
```
500515

@@ -637,7 +652,7 @@ vendor/bin/ib-cacher --clear
637652

638653
### `writer` - 文件產生器
639654

640-
為專案所有 ImmutableBase 子類物件產生文件,可產生 Mermaid 類別圖及 Markdown 屬性表
655+
為專案所有 ImmutableBase 子類物件產生文件,可產生 Mermaid 類別圖、Markdown 屬性表及 TypeScript 型別宣告
641656

642657
```bash
643658
vendor/bin/ib-writer
@@ -720,7 +735,7 @@ vendor/bin/ib-writer
720735
## 注意事項
721736

722737
1. 所有子類屬性必須為 public;由於 ImmutableBase 為 readonly class,整條繼承鏈在 PHP 語言層級也必須是 readonly。
723-
2. 此體系子物件所有屬性型別禁止設為:`iterable``object`、非 ImmutableBase 子類或非 Enum 的類,如:`DateTime``Closure`
738+
2. 此體系子物件所有屬性型別禁止設為:`null``iterable``object`、非 ImmutableBase 子類或非 Enum 的類,如:`DateTime``Closure`
724739
3. Enum 屬性接受 case 名稱(`"HIGH"`)或 backed 值(`3`),解析後的屬性值始終為 Enum 實例。
725740
4. 支援 `mixed` 型別,但值不會被進行驗證。
726741

bin/ib-writer

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ if (
5757
[
5858
'mmd' => 'mermaid',
5959
'md' => 'markdown',
60+
'ts' => 'typescript',
6061
],
6162
)
6263
) &&

src/Attributes/ArrayOf.php

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,32 @@
11
<?php
22

3+
declare (strict_types = 1);
4+
35
namespace ReallifeKip\ImmutableBase\Attributes;
46

7+
use ReallifeKip\ImmutableBase\Enums\Native;
8+
59
/**
610
* Declares that an array property contains typed elements of a specific
7-
* ImmutableBase subclass. The target class is passed as the first
8-
* constructor argument of the attribute.
11+
* ImmutableBase subclass, or validated PHP scalar values via Native.
12+
*
13+
* Accepts either a class-string of an ImmutableBase subclass, or a
14+
* Native enum case (Native::string, Native::int, Native::float, Native::bool)
15+
* for primitive typed arrays.
916
*
1017
* Must be applied to properties typed exactly as `array`. Union types
1118
* or non-array types will trigger InvalidArrayOfUsageException at scan time.
1219
*
1320
* @example #[ArrayOf(OrderItemDTO::class)] public array $items
21+
* @example #[ArrayOf(Native::string)] public array $tags
22+
* @example #[ArrayOf(Native::int)] public array $scores
1423
*/
1524
#[\Attribute(\Attribute::TARGET_PROPERTY)]
1625
final class ArrayOf
1726
{
18-
/** @param class-string $class */
27+
/**
28+
* @param class-string|Native $class FQCN of an ImmutableBase subclass, or a Native enum case for primitive typed arrays.
29+
*/
1930
private function __construct(
2031
public string $class
2132
) {}

src/Attributes/Defaults.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
<?php
22

3+
declare (strict_types = 1);
4+
35
namespace ReallifeKip\ImmutableBase\Attributes;
46

57
/**

src/Attributes/Spec.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
<?php
22

3+
declare (strict_types = 1);
4+
35
namespace ReallifeKip\ImmutableBase\Attributes;
46

57
/**

src/Attributes/ValidateFromSelf.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
<?php
22

3+
declare (strict_types = 1);
4+
35
namespace ReallifeKip\ImmutableBase\Attributes;
46

57
/**

src/BasicTrait.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,9 @@ trait BasicTrait
2525
* @param ReflectionClass|ReflectionProperty $target Reflection target to inspect.
2626
* @param class-string $name Fully-qualified attribute class name.
2727
* @param bool $getFirst Whether to return only the first argument.
28-
* @return mixed|null
28+
* @return mixed
2929
*/
30-
public static function getAttributeArgument(ReflectionClass | ReflectionProperty $target, string $name, bool $getFirst = true)
30+
public static function getAttributeArgument(ReflectionClass | ReflectionProperty $target, string $name, bool $getFirst = true): mixed
3131
{
3232
if ($value = $target->getAttributes($name)) {
3333
$value = $value[0];

src/CLI/Cacher.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
<?php
22

3+
declare (strict_types = 1);
4+
35
namespace ReallifeKip\ImmutableBase\CLI;
46

57
use Composer\Autoload\ClassLoader;
@@ -132,7 +134,7 @@ private function indexDirectory(string $dir): void
132134
* its source. Resolves namespace and class name from T_NAMESPACE and
133135
* T_CLASS tokens respectively.
134136
*
135-
* @param string $path Absolute file path.
137+
* @param string $content Full PHP file content.
136138
* @return list<class-string>
137139
*/
138140
private static function parseFullClassname(string $content): array
@@ -187,7 +189,7 @@ private static function parseFullClassname(string $content): array
187189
* @param ReflectionProperty[] $properties The name of the property being validated.
188190
* @return void
189191
*/
190-
private static function defaultValueValidate(string $classname, array $defaults, array $properties)
192+
private static function defaultValueValidate(string $classname, array $defaults, array $properties): void
191193
{
192194
foreach ($properties as $property) {
193195
$name = $property->name;

0 commit comments

Comments
 (0)