PHP 8.5 Clone With for Readonly Objects

PHP 8.5 extends object cloning with a second argument that replaces selected properties on the new object. The syntax clone($object, $properties) is especially useful for readonly value objects, where you want an updated copy without changing the original instance.

This feature removes repetitive copy constructors and long “with” methods. Existing forms such as clone $object continue to work, so you can adopt the new form only where property replacement improves clarity.

Version requirement: The two-argument clone() form requires PHP 8.5 or newer.

PHP 8.5 Clone With Syntax

The first argument is the object to copy. The second is an associative array whose keys are property names and whose values replace those properties on the clone.

Syntax:

<?php
// Copy an object and replace selected properties on the copy.
$copy = clone($object, [
    'propertyName' => $newValue,
]);

PHP performs a normal shallow clone first, calls __clone() when the class defines it, and then applies replacements in the array's iteration order. Property types, visibility, hooks, __set(), and dynamic-property rules still apply. The readonly state is temporarily unlocked for these assignments.

Create an Updated Readonly Value Object

A readonly object cannot be changed after construction. Place a focused “with” method inside the class to validate the new value and return an updated copy.

Example:

<?php
readonly class DeliveryOption
{
    public function __construct(
        public string $city,
        public int $fee,
        public string $speed = 'standard',
    ) {}

    public function withFee(int $fee): self
    {
        // Protect the object's rule before creating the new copy.
        if ($fee < 0) {
            throw new InvalidArgumentException('Fee cannot be negative.');
        }

        return clone($this, ['fee' => $fee]);
    }
}

$indore = new DeliveryOption('Indore', 80);
$expressOffer = $indore->withFee(120);

// The original and copy remain independent values.
echo $indore->fee . PHP_EOL;
echo $expressOffer->fee . PHP_EOL;

Output:

80
120

Replace More Than One Property

The replacement array may contain several properties. A named method keeps business rules inside the class and gives the change a meaningful name.

Example:

<?php
readonly class Meeting
{
    public function __construct(
        public string $host,
        public string $room,
        public string $time,
    ) {}

    public function reschedule(string $room, string $time): self
    {
        // Replace both related values in one cloned result.
        return clone($this, [
            'room' => $room,
            'time' => $time,
        ]);
    }
}

$original = new Meeting('Oliver', 'Room A', '10:00');
$revised = $original->reschedule('Room C', '11:30');

echo $original->room . ' at ' . $original->time . PHP_EOL;
echo $revised->room . ' at ' . $revised->time . PHP_EOL;

Output:

Room A at 10:00
Room C at 11:30

Understand __clone() Execution Order

The magic __clone() method runs before the replacement array is applied. Therefore, an explicit replacement can overwrite a value set by __clone().

Example:

<?php
class Report
{
    public string $status = 'original';

    public function __clone(): void
    {
        // This runs first for every clone.
        $this->status = 'cloned';
    }

    public function withStatus(string $status): self
    {
        // This replacement runs after __clone().
        return clone($this, ['status' => $status]);
    }
}

$report = new Report();
$reviewed = $report->withStatus('reviewed');
echo $reviewed->status;

Output:

reviewed

Property Rules Still Apply

Clone with is not a way to bypass a class design. PHP processes replacements as normal assignments, apart from allowing readonly properties to receive their new value during cloning.

  • A value must satisfy the property's declared type.
  • The call site must have permission to write the property.
  • A set hook runs when the replacement targets a hooked property.
  • An inaccessible property causes an error.
  • Replacements run in array order, and the first failing assignment stops the operation.

Tip: Put clone-with calls inside descriptive methods such as withFee() or reschedule(). The method can validate values and protect class invariants before PHP creates the result.

Remember That Cloning Is Shallow

Like ordinary cloning, clone with initially copies property values shallowly. If a property contains another object, both outer objects still refer to that nested object unless __clone() explicitly clones it or the replacement array supplies a different instance.

Example:

<?php
class Address
{
    public function __construct(public string $city) {}
}

class Customer
{
    public function __construct(
        public string $name,
        public Address $address,
    ) {}
}

$customer = new Customer('James', new Address('London'));
$copy = clone($customer, ['name' => 'Aarav']);

// Both customers still reference the same Address object.
$copy->address->city = 'Leeds';
echo $customer->address->city;

Output:

Leeds

When to Use Clone With

Use the feature for immutable configuration, value objects, request or response objects, and state transitions where retaining the original value matters. Prefer a constructor when you are creating a conceptually new object, and use a dedicated method when validation or related property changes must stay together.

Conclusion

PHP 8.5 clone with creates a copy and replaces selected properties in a concise operation. It works particularly well for readonly value objects, but it still respects types, visibility, hooks, and shallow-clone behavior. Keep replacements behind meaningful methods, validate new values, and confirm that shared nested objects are intentional.



Found This Page Useful? Share It!
Get the Latest Tutorials and Updates
Join us on Telegram