Skip to content

ReallifeKip/ImmutableBase

Repository files navigation

ImmutableBase

License: MIT PHP Version Support Packagist Version

FOSSA Status Coverage

Quality Gate Status Bugs Code Smells Duplicated Lines (%) Reliability Rating Security Rating Technical Debt Maintainability Rating Vulnerabilities

CI Downloads

一個專為 不可變物件(Immutable Object) 設計的抽象基底類別,適用於 DTO(Data Transfer Object)VO(Value Object) 等需要「一次初始化、不可更改」的場景。

此類別強調資料的不可變性(Immutability)類型安全(Type Safety),並可透過建構式快速初始化、內建型別自動轉換機制、淺層複製 (with) 以及自動序列化支援 (toArray, jsonSerialize)。


特性總覽

  • Constructor 自動對應屬性並進行型別驗證
  • 支援 readonly 行為邏輯(非語法)
  • 支援 ReflectionUnionType 型別解析
  • 遞迴初始化巢狀 ImmutableBase 子類
  • 支援 with([...]) 複製模式,包含嵌套物件更新
  • 自動 toArray()jsonSerialize()
  • 架構模式標註:#[DataTransferObject]#[ValueObject]#[Entity] (必須)
  • 陣列自動實例化:#[ArrayOf] 含類型驗證
  • 強制屬性訪問控制與型別安全

測試

單元測試

vendor/bin/phpunit tests

效能測試

vendor/bin/phpbench run

使用方式

1. 建立你的不可變物件類別

use ReallifeKip\ImmutableBase\ImmutableBase;
use ReallifeKip\ImmutableBase\DataTransferObject;

#[DataTransferObject]
final class UserDTO extends ImmutableBase
{
    public readonly string $name;
    public readonly int $age;
    public readonly ?ProfileDTO $profile = null;
}

2. 初始化(建構子將自動賦值)

$user = new UserDTO([
    'name' => 'Alice',
    'age' => 30,
]);

3. 使用 with() 建立修改版

// 簡單屬性更新
$olderUser = $user->with(['age' => 31]);

// 嵌套物件部分更新(v2.0.0 新功能)
$userWithNewAddress = $user->with([
    'profile' => [
        'address' => [
            'city' => '台北市',
            'zipCode' => '10001'
        ]
    ]
]);

4. 輸出為陣列或 JSON

$user->toArray();         // ['name' => 'Alice', 'age' => 30]
json_encode($user);       // '{"name":"Alice","age":30}'

架構模式標註(必須使用)

⚠️ 重要:v2.3.0 開始,所有 ImmutableBase 子類都必須使用以下其中一個標註。

#[DataTransferObject] - 資料傳輸物件

所有屬性必須為 public readonly,適用於跨層傳輸資料:

use ReallifeKip\ImmutableBase\DataTransferObject;

#[DataTransferObject]
class UserDTO extends ImmutableBase
{
    public readonly string $name;
    public readonly int $age;
    public readonly string $email;
}

#[ValueObject] - 值物件

所有屬性必須為 private,適用於領域驅動設計中的值物件:

use ReallifeKip\ImmutableBase\ValueObject;

#[ValueObject]
class Money extends ImmutableBase
{
    private int $amount;
    private string $currency;

    public function getAmount(): int
    {
        return $this->amount;
    }

    public function getCurrency(): string
    {
        return $this->currency;
    }
}

#[Entity] - 實體物件

所有屬性必須為 private,適用於領域驅動設計中的實體:

use ReallifeKip\ImmutableBase\Entity;

#[Entity]
class User extends ImmutableBase
{
    private string $id;
    private string $email;
    private string $name;

    public function getId(): string
    {
        return $this->id;
    }

    public function getEmail(): string
    {
        return $this->email;
    }
}

#[ArrayOf] - 陣列自動實例化

指定陣列屬性中每個元素的類別,自動將陣列數據轉換為指定類別的實例。 v2.3.0 新增嚴格類型驗證:

use ReallifeKip\ImmutableBase\ArrayOf;
use ReallifeKip\ImmutableBase\DataTransferObject;

#[DataTransferObject]
class UserListDTO extends ImmutableBase
{
    #[ArrayOf(UserDTO::class)]  // 必須是 ImmutableBase 子類
    public readonly array $users;

    #[ArrayOf(TagDTO::class)]
    public readonly array $tags;
}

// 使用方式
$userList = new UserListDTO([
    'users' => [
        ['name' => 'Alice', 'age' => 30],
        ['name' => 'Bob', 'age' => 25]
    ],
    'tags' => [
        ['name' => 'developer'],
        ['name' => 'senior']
    ]
]);

// users 和 tags 陣列中的每個元素都會自動轉換為對應的 DTO 實例

實作原則與行為說明

Constructor 行為

建構子將透過 Reflection 掃描所有屬性,並依照 $data 自動賦值,型別不合將主動拋出例外。

throw new Exception("age 型別錯誤,期望:int,傳入:string。");

支援 Union Type(int|string 等)

public int|string $value;

$data['value'] 為整數或字串皆可;若皆不符合,將拋出型別錯誤。


巢狀 ImmutableBase 支援

若某屬性為另一個 ImmutableBase 子類:

// DataTransferObject 模式
public readonly AddressDTO $address;

// 或 ValueObject/Entity 模式
private AddressDTO $address;

則若 $data['address'] 為 array,會自動遞迴初始化:

new AddressDTO([...])

v2.0.0 新功能with() 方法現在支援嵌套更新:

$user = $user->with([
    'address' => [
        'city' => '新城市'  // 只更新地址的城市,其他屬性保持不變
    ]
]);

不可變與 with() 設計

  • with(array $data):建立 修改後的新實體,不影響原始物件
  • 支援嵌套物件的部分更新,會遞迴調用嵌套物件的 with() 方法
  • 使用 Reflection 直接處理屬性,確保型別安全

自動 toArray()jsonSerialize()

  • 支援嵌套 ImmutableBase 物件的遞迴序列化
  • json_encode() 等同於 toArray() 的輸出

注意事項

  1. 型別宣告為必要:未宣告型別會導致 Reflection 錯誤
  2. 不支援可變屬性:務必遵守 Immutable 設計原則
  3. 不支援 constructor injection:請以 $data array 傳入
  4. 屬性標註規則
    • 推薦:使用架構模式標註 #[DataTransferObject]#[ValueObject]#[Entity]
    • 陣列處理:使用 #[ArrayOf(ClassName::class)] 進行陣列自動實例化
    • DataTransferObject 要求所有屬性為 public readonly
    • ValueObject 和 Entity 要求所有屬性為 private

完整範例

<?php

use ReallifeKip\ImmutableBase\ImmutableBase;
use ReallifeKip\ImmutableBase\DataTransferObject;
use ReallifeKip\ImmutableBase\ValueObject;
use ReallifeKip\ImmutableBase\ArrayOf;

#[DataTransferObject]
class AddressDTO extends ImmutableBase
{
    public readonly string $city;
    public readonly string $zipCode;
}

#[DataTransferObject]
class TagDTO extends ImmutableBase
{
    public readonly string $name;
    public readonly ?string $color = null;
}

#[DataTransferObject]
class ProfileDTO extends ImmutableBase
{
    public readonly AddressDTO $address;
    public readonly ?string $phone = null;

    #[ArrayOf(TagDTO::class)]
    public readonly array $tags;
}

#[ValueObject]
class UserId extends ImmutableBase
{
    private string $value;

    public function getValue(): string
    {
        return $this->value;
    }
}

#[DataTransferObject]
class UserDTO extends ImmutableBase
{
    public readonly string $name;
    public readonly int $age;
    public readonly ?ProfileDTO $profile = null;
    public readonly UserId $id;
}

// 初始化
$user = new UserDTO([
    'id' => ['value' => 'user_123'],
    'name' => 'Alice',
    'age' => 30,
    'profile' => [
        'address' => [
            'city' => '台北市',
            'zipCode' => '10001'
        ],
        'phone' => '0912-345-678',
        'tags' => [
            ['name' => 'developer', 'color' => 'blue'],
            ['name' => 'senior', 'color' => 'gold']
        ]
    ]
]);

// 嵌套更新
$relocatedUser = $user->with([
    'profile' => [
        'address' => [
            'city' => '高雄市',
            'zipCode' => '80001'
        ]
    ]
]);

// 輸出
echo json_encode($relocatedUser, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);

授權 License

本套件使用 MIT License


開發者資訊

Kip 開發與維護,適用於 Laravel、DDD、Hexagonal Architecture 等架構中 Immutable DTO/VO 實作需求。


如果有任何建議或發現錯誤,歡迎開 PR 或提出 Issue

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages