Skip to content

Static Mutations

The Mutator class provides static methods to create modified copies of any object or array. Use it when working with third-party classes, stdClass, or when you need a standalone mutation utility.

The simplest way to create a modified copy:

<?php
use Cline\Immutable\Mutator;
// Mutate an array
$original = ['name' => 'John', 'age' => 30];
$updated = Mutator::mutate($original, ['age' => 31]);
// Original unchanged
$original; // ['name' => 'John', 'age' => 30]
$updated; // ['name' => 'John', 'age' => 31]
// Mutate an object
$user = new UserData('John', 'john@example.com', 30);
$updated = Mutator::mutate($user, ['age' => 31]);

For more complex mutations, use the fluent interface:

Start a fluent chain:

$mutator = Mutator::for($original);

Add multiple properties to modify:

$updated = Mutator::for($user)
->with(['name' => 'Jane', 'age' => 25])
->get();

Add a single property:

$updated = Mutator::for($user)
->set('name', 'Jane')
->get();

Chain multiple calls together:

$updated = Mutator::for($user)
->set('name', 'Jane')
->with(['email' => 'jane@example.com'])
->set('age', 25)
->get();

Later mutations override earlier ones:

$updated = Mutator::for(['name' => 'John'])
->set('name', 'Jane')
->set('name', 'Bob')
->get();
$updated['name']; // 'Bob'

Execute the mutation and return the result:

$result = Mutator::for($data)->with($changes)->get();
$original = ['a' => 1, 'b' => 2];
$updated = Mutator::mutate($original, ['a' => 10]);
// ['a' => 10, 'b' => 2]
$original = ['a' => 1, 'b' => 2];
$updated = Mutator::mutate($original, ['c' => 3]);
// ['a' => 1, 'b' => 2, 'c' => 3]
$original = [1, 2, 3];
$updated = Mutator::mutate($original, [0 => 10]);
// [10, 2, 3]

Replace entire nested structures:

$original = ['user' => ['name' => 'John', 'age' => 30]];
$updated = Mutator::mutate($original, [
'user' => ['name' => 'Jane', 'age' => 25]
]);
// ['user' => ['name' => 'Jane', 'age' => 25]]

The Mutator automatically delegates to the object’s mutate() method:

$user = new UserData('John', 'john@example.com', 30);
$updated = Mutator::mutate($user, ['age' => 31]);
// Equivalent to:
$updated = $user->mutate(['age' => 31]);

Works with any object using reflection:

class GenericObject
{
public function __construct(
public string $name,
public int $value,
) {}
}
$obj = new GenericObject('test', 42);
$updated = Mutator::mutate($obj, ['value' => 100]);
$obj->value; // 42
$updated->value; // 100

Full support for dynamic objects:

$obj = new stdClass();
$obj->name = 'John';
$obj->age = 30;
$updated = Mutator::mutate($obj, ['age' => 31]);
$updated->age; // 31
// Can add new properties
$updated = Mutator::mutate($obj, ['email' => 'john@example.com']);
$updated->email; // 'john@example.com'

For objects with type hints, the Mutator validates values:

use Cline\Immutable\Exceptions\InvalidTypeException;
$obj = new GenericObject('test', 42);
// Throws InvalidTypeException
Mutator::mutate($obj, ['value' => 'not an int']);
use Cline\Immutable\Exceptions\PropertyDoesNotExistException;
$obj = new GenericObject('test', 42);
// Throws PropertyDoesNotExistException
Mutator::mutate($obj, ['nonexistent' => 'value']);

Note: stdClass allows any property name without validation.

Real-world usage with operation state transitions:

$operation = new OperationData(
id: 'op-123',
function: 'process',
version: '1.0.0',
status: OperationStatus::Running,
progress: 50,
result: null,
errors: [],
startedAt: $now,
completedAt: null,
cancelledAt: null,
metadata: ['key' => 'value'],
);
// Without immutable mutation (verbose):
$cancelledOperation = new OperationData(
id: $operation->id,
function: $operation->function,
version: $operation->version,
status: OperationStatus::Cancelled,
progress: $operation->progress,
result: $operation->result,
errors: $operation->errors,
startedAt: $operation->startedAt,
completedAt: $operation->completedAt,
cancelledAt: new DateTimeImmutable(),
metadata: $operation->metadata,
);
// With Mutator (clean):
$cancelledOperation = Mutator::mutate($operation, [
'status' => OperationStatus::Cancelled,
'cancelledAt' => new DateTimeImmutable(),
]);

For Laravel applications, use the facade:

<?php
use Cline\Immutable\Facades\Mutator;
$updated = Mutator::mutate($user, ['age' => 31]);
$updated = Mutator::for($user)
->with(['age' => 31])
->get();