Skip to content

Lightweight abstract classes for building immutable PHP objects.

License

ReallifeKip/ImmutableBase

Repository files navigation

ImmutableBase

🌐 Available in other languages: 繁體中文

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

An abstract base class designed for immutable objects, suitable for DTOs (Data Transfer Objects) and VOs (Value Objects) where data is initialized once and cannot be changed.

Focuses on immutability and type safety, with APIs that make it easy to construct immutable objects.

Overview

  1. Build objects via static constructors; ImmutableBase scans incoming keys/values and returns an instance.
  2. If a value’s type does not match the declared property type, an exception is thrown with detailed class/property info.
  3. Supports all PHP built-in types, Enums, instances, and union types.
  4. For properties typed as subclasses of ImmutableBase, arrays/objects matching the declared structure are automatically instantiated.

Installation

composer require reallifekip/immutable-base

Example

use ReallifeKip\ImmutableBase\Attributes\ArrayOf;
use ReallifeKip\ImmutableBase\Objects\DataTransferObject;

class UserDTO extends DataTransferObject
{
    public readonly string $name;
    public readonly int $age;
}

class UserListDTO extends DataTransferObject
{
    #[ArrayOf(UserDTO::class)]
    public readonly array $users;
}

$userList = UserListDTO::fromArray([
    'users' => [
        ['name' => 'Alice', 'age' => 18],
        '{"name": "Bob", "age": 19}',
        UserDTO::fromArray(['name' => 'Carl', 'age' => 20]),
        UserDTO::fromJson('{"name": "Dave", "age": 21}')
    ]
]);
print_r($userList);

Testing

Unit tests

vendor/bin/phpunit tests

Benchmarks

vendor/bin/phpbench run

Object Types

Data Transfer Object

use ReallifeKip\ImmutableBase\Objects\DataTransferObject;

final class UserDTO extends DataTransferObject
{
    public readonly string $name;
    public readonly int $age;
}

Value Object

use ReallifeKip\ImmutableBase\Objects\ValueObject;
final class Money extends ValueObject
{
    private readonly int $value;
}

API

Constructing — fromArray(), fromJson()

When scanning input, if a key is not a declared property of the class, it is ignored and will not exist on the resulting instance.

$user = User::fromArray([
    'name' => 'Kip',
    'age' => 18
]);
$user = Money::fromJson('{"value": 1000}');

Updating — with()

⚠️ This does not mutate the original object. A new instance is returned with partial updates, by design. For the underlying rationale, see Objects and references.
⚠️ When with() targets a #[ArrayOf] property, the array is rebuilt.
Keys that are not declared properties are ignored and will not appear on the new instance.

// Update a scalar property
$newUser = $user->with([
    'name' => 'someone'
]);

// Partial update of a nested object
$userWithNewAddress = $user->with([
    'profile' => [
        'address' => 'Taipei City'
    ]
]);

Exporting — toArray()

// ['name' => 'Kip', 'age' => 18]
$user->toArray();

Architecture: Attributes

#[DataTransferObject]

⚠️ Will be deprecated in v4.0.0. See Architecture: Inheritance for the new approach.

All properties must be public readonly. Intended for cross-layer data transport.

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

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

#[ValueObject]

⚠️ Will be deprecated in v4.0.0. See Architecture: Inheritance.

All properties must be private. Intended for value objects in DDD.

use ReallifeKip\ImmutableBase\ValueObject;
use ReallifeKip\ImmutableBase\ImmutableBase;

#[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]

⚠️ Will be deprecated in v4.0.0. See Architecture: Inheritance.

All properties must be private. Intended for entities in DDD.

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] — Array auto-instantiation

⚠️ When with() targets a #[ArrayOf] property, the array is rebuilt.

Marks an array property as an array of instances. Incoming data for that array will be converted into instances of the specified class. Accepts JSON strings, arrays, or already-instantiated objects that match the required structure.

use ReallifeKip\ImmutableBase\Attributes\ArrayOf;
use ReallifeKip\ImmutableBase\Objects\DataTransferObject;

class UserListDTO extends DataTransferObject
{
    #[ArrayOf(UserDTO::class)]
    public readonly array $users;
}

$userList = UserListDTO::fromArray([
    'users' => [
        // All four forms are accepted
        ['name' => 'Alice', 'age' => 18],
        '{"name": "Bob", "age": 19}',
        UserDTO::fromArray(['name' => 'Carl', 'age' => 20]),
        UserDTO::fromJson('{"name": "Dave", "age": 21}')
    ]
]);

Architecture: Inheritance

DataTransferObject

All properties must be public readonly. Intended for cross-layer data transport.

use ReallifeKip\ImmutableBase\Objects\DataTransferObject;

class UserDTO extends DataTransferObject
{
    public readonly string $name;
    public readonly int $age;
    public readonly string $email;
}

ValueObject

All properties must be private readonly. Intended for value objects in DDD.

use ReallifeKip\ImmutableBase\Objects\ValueObject;

class Money extends ValueObject
{
    private int $value;
    public function getValue(): int
    {
        return $this->value;
    }
}

Notes

  1. Property types must be explicitly declared; mixed is not allowed.
  2. Enums: when a property is typed as an Enum, construction validates incoming data against the Enum’s cases/values and the resulting property is the Enum instance. Use string types if you want raw text values.

License

This package is released under the MIT License.

Maintainer

Developed and maintained by Kip. Suitable for implementing immutable DTOs/VOs in Laravel, DDD, and Hexagonal Architecture.


Feedback and contributions are welcome — please open an Issue or submit a PR.