ImmutableBase
Lightweight abstract classes for building immutable PHP objects.
Install / Use
/learn @ReallifeKip/ImmutableBaseREADME
ImmutableBase
🌐 Available in other languages: 繁體中文
A PHP library for building immutable data objects with strict type validation, designed for DTOs (Data Transfer Objects), VOs (Value Objects), and SVOs (Single Value Objects).
Focuses on immutability, type safety, and deep structural operations - including nested construction, dot, path mutation, and recursive equality comparison.
Why ImmutableBase?
🚀 Efficient Automatic Construction
// 🥳 ImmutableBase requires no boilerplate constructors. Pass an array or JSON to construct, with no ordering constraints on input keys.
readonly class Order extends DataTransferObject
{
public string $date;
public string $time;
}
Order::fromArray($data); // $data must be an array (use fromJson() for JSON strings)
// 🫤 The conventional approach requires writing constructors manually, cannot directly accept external array or JSON data for construction.
class Order extends DataTransferObject
{
public function __construct(
public readonly string $date,
public readonly string $time
){}
}
new Order('2026-01-01', '00:00:00', ...); // Cannot directly accept external array or JSON data, and risks argument misordering if parameter names are not explicitly specified
🛡️ Declarative Default Values
// 🥳 ImmutableBase fills missing properties from defaultValues() or #[Defaults], with clear priority and null-awareness.
readonly class CreateUserDTO extends DataTransferObject
{
public string $name;
#[Defaults('member')]
public string $role;
public static function defaultValues(): array
{
return ['role' => 'admin']; // Takes precedence over #[Defaults]
}
}
CreateUserDTO::fromArray(['name' => 'Kip']); // role = 'admin'
// 🫤 The conventional approach requires manual null-coalescing or constructor defaults, with no centralized declaration.
class CreateUserDTO {
public function __construct(
public readonly string $name,
public readonly string $role = 'member', // Cannot be overridden per-class without rewriting constructors
){}
}
🔧 Flexible Deep Path Updates
Update deeply nested properties by path - no Russian nesting dolls.
// 🥳 ImmutableBase is flexible and precise.
$order->with(['items.0.count' => 1]); // Target a specific array index and update count directly
// 🫤 The conventional approach is verbose and cannot preserve other elements in the original array.
$order->with([
'items' => [
[
'count' => 1
]
]
])
🔎 Intuitive Error Tracing
// 🥳 ImmutableBase pinpoints the exact error location.
SomeException: Order > $profile > 0 > $count > {error message}
// 🫤 The conventional approach only provides vague or hard-to-trace messages.
SomeException: {error message}
⚡ Lightning-Fast Startup
🥳 ImmutableBase can scan and generate a metadata cache file ib-cache.php via vendor/bin/ib-cacher, maximizing startup performance.
🫤 The conventional approach may lack any caching mechanism, paying the cost of reflection on every request.
🔗 Automatic and Controllable Validation Chain
🥳 ImmutableBase's ValueObject and SingleValueObject support an optional validate(): bool method. During construction, the entire inheritance chain is automatically traversed top-down for validation. Apply #[ValidateFromSelf] to reverse the direction.
🫤 The conventional approach rarely offers an automatic validation chain - validation logic must be manually wired in constructors.
📃 Documentation as Code, Code as Documentation
🥳 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.
🫤 The conventional approach cannot guarantee consistency between code and documentation.
🆓 Highly Compatible, Lightweight, Zero Dependencies
🥳 ImmutableBase requires no additional dependencies and is not tied to any framework when used without documentation generation, caching, or testing.
🫤 The conventional approach, when coupled to a specific package or framework, is difficult to decouple quickly.
📦 Controllable Data Output
// 🥳 ImmutableBase uses `#[KeepOnNull]` and `#[SkipOnNull]` to precisely control whether null properties appear in output - no manual filtering needed.
#[SkipOnNull]
readonly class User extends ValueObject
{
#[KeepOnNull]
public ?string $name;
public ?int $age;
}
User::fromArray([])->toArray(); // ["name" => null]
// 🫤 The conventional approach typically requires manually filtering out null values.
readonly class User extends ValueObject
{
public ?string $name;
public ?int $age;
}
$user = new User();
$data = get_object_vars($user);
$data['name'] ??= null;
⭐ TypeScript-Like Type Narrowing
// 🥳 ImmutableBase constrains SingleValueObject to declare $value, but allows flexible type definitions. (Achieved via interface + hooked property with zero reflection overhead)
readonly class ValidAge extends SingleValueObject
{
public int $value; // Semantically correct type matching the object's purpose
}
// 🫤 The conventional approach locks the type in the parent class with no way to customize it. Parents typically declare mixed or overly broad union types, making SVO design difficult.
class ValidAge extends SingleValueObject
{
public string $value; // Type locked by parent - cannot be changed, semantically mismatched
}
Installation
composer require reallifekip/immutable-base
Requires PHP 8.4+.
Quick Example
use ReallifeKip\ImmutableBase\Attributes\ArrayOf;
use ReallifeKip\ImmutableBase\Objects\DataTransferObject;
use ReallifeKip\ImmutableBase\Objects\ValueObject;
use ReallifeKip\ImmutableBase\Objects\SingleValueObject;
readonly class ValidAge extends SingleValueObject
{
public int $value;
public function validate(): bool
{
return $this->value >= 18;
}
}
readonly class User extends ValueObject
{
public string $name;
public ValidAge $age;
public function validate(): bool
{
return mb_strlen($this->name) >= 2;
}
}
readonly class SignUpUsersDTO extends DataTransferObject
{
#[ArrayOf(User::class)]
public array $users;
public int $userCount;
}
$signUp = SignUpUsersDTO::fromArray([
'users' => [
['name' => 'ReallifeKip', 'age' => 18], // array
'{"name": "Bob", "age": 19}', // JSON string
User::fromArray(['name' => 'Carl', 'age' => 20]), // instance via fromArray
User::fromJson('{"name": "Dave", "age": 21}'), // instance via fromJson
],
'userCount' => 4,
]);
🔗 Want a quick try? JSON to ImmutableBase Converter lets you paste JSON and generate IB classes instantly!
Testing
# Unit tests
vendor/bin/phpunit tests
# Benchmarks
vendor/bin/phpbench run
Object Types
DataTransferObject (DTO)
A pure data structure for transport and interchange. Even if a validate(): bool meth
