objects namespaces and application architecture
Object Cloning and Comparison
Objects in PHP are assigned by reference to the same object handle. That means $b = $a does not create a second object. It creates another variable pointing at the same object.
This matters whenever objects are mutable. If two variables point at the same object, changing one changes what the other sees.
<?php
declare(strict_types=1);
final class Basket
{
/** @var list<string> */
public array $items = [];
}
$first = new Basket();
$second = $first;
$second->items[] = 'Book';
echo count($first->items) . PHP_EOL;
// Prints:
// 1
There was only one Basket object. Both variables pointed to it.
Cloning Creates A New Object
The clone keyword creates a shallow copy of an object. The new object has the same property values, but it is a different object.
<?php
declare(strict_types=1);
final class Basket
{
/** @var list<string> */
public array $items = [];
}
$first = new Basket();
$first->items[] = 'Book';
$second = clone $first;
$second->items[] = 'Pen';
echo count($first->items) . PHP_EOL;
echo count($second->items) . PHP_EOL;
// Prints:
// 1
// 2
The array property was copied, so changing the copied array did not change the original basket.
Cloning Is Shallow
Cloning is shallow when properties contain other objects. The outer object is copied, but nested objects are still shared unless you clone them yourself.
<?php
declare(strict_types=1);
final class Customer
{
public function __construct(
public string $email,
) {
}
}
final class Order
{
public function __construct(
public Customer $customer,
) {
}
}
$original = new Order(new Customer('ada@example.com'));
$copy = clone $original;
$copy->customer->email = 'changed@example.com';
echo $original->customer->email . PHP_EOL;
// Prints:
// changed@example.com
The Order was cloned, but both orders still pointed at the same Customer.
Custom Cloning With __clone()
Use __clone() when a cloned object needs to copy nested objects or reset identity.
<?php
declare(strict_types=1);
final class Customer
{
public function __construct(
public string $email,
) {
}
}
final class Order
{
public function __construct(
public ?int $id,
public Customer $customer,
) {
}
public function __clone(): void
{
$this->id = null;
$this->customer = clone $this->customer;
}
}
$original = new Order(100, new Customer('ada@example.com'));
$copy = clone $original;
$copy->customer->email = 'changed@example.com';
echo $original->id . ': ' . $original->customer->email . PHP_EOL;
echo ($copy->id ?? 'new') . ': ' . $copy->customer->email . PHP_EOL;
// Prints:
// 100: ada@example.com
// new: changed@example.com
Resetting the ID is common when cloning an entity to create a new unsaved record. Deep cloning nested objects prevents accidental shared mutation.
Comparing Objects With ===
=== checks whether two variables refer to the exact same object.
<?php
declare(strict_types=1);
final class Product
{
public function __construct(
public int $id,
) {
}
}
$a = new Product(10);
$b = $a;
$c = new Product(10);
echo $a === $b ? 'same object' : 'different object';
echo PHP_EOL;
echo $a === $c ? 'same object' : 'different object';
echo PHP_EOL;
// Prints:
// same object
// different object
Use === when identity matters.
Comparing Objects With ==
== compares object properties loosely. Two different objects of the same class can be equal with == if their properties match.
<?php
declare(strict_types=1);
final class Product
{
public function __construct(
public int $id,
) {
}
}
$a = new Product(10);
$b = new Product(10);
echo $a == $b ? 'equal values' : 'different values';
echo PHP_EOL;
echo $a === $b ? 'same object' : 'different object';
echo PHP_EOL;
// Prints:
// equal values
// different object
Be careful with == on objects. It can become surprising when objects have many properties, nested objects, or different classes.
Prefer Explicit Equality For Value Objects
For value objects, an explicit method such as equals() is often clearer than relying on ==.
<?php
declare(strict_types=1);
final readonly class Money
{
public function __construct(
public int $amountPence,
public string $currency,
) {
}
public function equals(self $other): bool
{
return $this->amountPence === $other->amountPence
&& $this->currency === $other->currency;
}
}
$first = new Money(1000, 'GBP');
$second = new Money(1000, 'GBP');
echo $first->equals($second) ? 'same value' : 'different value';
echo PHP_EOL;
// Prints:
// same value
The method states exactly what equality means for the concept.
What You Should Be Able To Do
After this lesson, you should be able to explain that assignment shares the same object, clone creates a shallow copy, __clone() customises cloning, === checks object identity, and == compares object properties.
For junior work, the practical skill is avoiding accidental shared mutation and choosing explicit equality checks when business meaning matters.
Practice
Practice: Clone An Order Safely
Create a small PHP example showing safe cloning and explicit comparison.
Task
Build:
- a mutable
Customerobject - an
Orderobject with an optional ID and aCustomer Order::__clone()that resets the ID and clones the customer- a value object with an
equals()method
Use strict types. Keep the expected output in the PHP code block as printed lines or comments.
Check Your Work
Run cases for:
- cloning an order and changing the copied customer email
- proving the original customer email did not change
- comparing two equal value objects with
equals()
Afterward, explain why clone needed __clone() in this example.
Show solution
This solution customises cloning so the copied order becomes a new unsaved order and does not share the same mutable customer object.
<?php
declare(strict_types=1);
final class Customer
{
public function __construct(
public string $email,
) {
}
}
final class Order
{
public function __construct(
public ?int $id,
public Customer $customer,
) {
}
public function __clone(): void
{
$this->id = null;
$this->customer = clone $this->customer;
}
}
final readonly class Money
{
public function __construct(
public int $amountPence,
public string $currency,
) {
}
public function equals(self $other): bool
{
return $this->amountPence === $other->amountPence
&& $this->currency === $other->currency;
}
}
$original = new Order(100, new Customer('ada@example.com'));
$copy = clone $original;
$copy->customer->email = 'copy@example.com';
echo $original->id . ': ' . $original->customer->email . PHP_EOL;
echo ($copy->id ?? 'new') . ': ' . $copy->customer->email . PHP_EOL;
$firstTotal = new Money(2500, 'GBP');
$secondTotal = new Money(2500, 'GBP');
echo $firstTotal->equals($secondTotal) ? 'same value' : 'different value';
echo PHP_EOL;
// Prints:
// 100: ada@example.com
// new: copy@example.com
// same value
__clone() is needed because normal cloning is shallow. Without it, the original and copied orders would share the same mutable Customer object.