spatie/php-cloneable
Trait for PHP 8.1+ that makes objects with readonly properties cloneable. Safely “clone with changes” by copying an object while overriding readonly fields—handy until PHP gets native clone-with support.
Installation:
composer require spatie/php-cloneable
Add the trait to your model or entity class:
use Spatie\Cloneable\Cloneable;
class User implements \Cloneable
{
use Cloneable;
public function __construct(
public readonly string $id,
public readonly string $name,
) {}
}
First Use Case: Clone an object with readonly properties:
$original = new User(id: '1', name: 'John');
$clone = clone $original;
// Modify clone without affecting original
$clone->name = 'Jane'; // ❌ Error (readonly)
Mark Properties as Readonly:
class Product
{
use Cloneable;
public function __construct(
public readonly int $id,
public string $name,
) {}
}
Clone with Deep Copy:
$product = new Product(id: 1, name: 'Laptop');
$copy = clone $product;
$copy->name = 'Phone'; // Works (non-readonly)
Handle Nested Objects:
class Order
{
use Cloneable;
public function __construct(
public readonly int $id,
public Product $product,
) {}
}
The trait recursively clones nested Cloneable objects.
Laravel Eloquent Models:
Extend the trait in models with readonly fields (e.g., created_at):
class Post extends Model
{
use Cloneable;
protected $attributes = [
'created_at' => 'readonly',
];
}
Note: Override __clone() if custom logic is needed.
DTOs/Data Transfer Objects: Ideal for immutable DTOs:
class UserDTO
{
use Cloneable;
public function __construct(
public readonly string $uuid,
public readonly array $metadata,
) {}
}
Collections:
Clone collections of Cloneable objects:
$users = collect([new User('1', 'Alice'), new User('2', 'Bob')]);
$clonedUsers = $users->map(fn ($user) => clone $user);
Non-Cloneable Nested Objects:
If a nested property isn’t Cloneable, cloning fails silently. Validate dependencies:
class InvalidOrder
{
use Cloneable;
public function __construct(
public readonly NonCloneableClass $item, // ❌ Fails
) {}
}
Circular References:
The trait doesn’t handle circular references (e.g., User referencing Order which references User). Use __clone() overrides or external libraries like dcopy for complex cases.
PHP 8.0+ Only: The package targets PHP 8.1+. Downgrading breaks functionality.
Verify Cloning:
Override __clone() to log clones:
protected function __clone()
{
\Log::debug('Cloning ' . static::class);
}
Check Readonly Properties:
Use var_dump((new \ReflectionClass($obj))->getProperty('prop')->isReadonly()) to confirm readonly status.
Custom Cloning Logic:
Override __clone() for custom behavior:
protected function __clone()
{
$this->resetCache(); // Example side effect
}
Exclude Properties:
Use shouldSkipCloning() to exclude properties:
protected function shouldSkipCloning(string $property): bool
{
return $property === 'tempToken';
}
Post-Clone Initialization:
Add a postClone() method:
protected function postClone()
{
$this->generateToken();
}
No Configuration: The trait is zero-config. All behavior is controlled via method overrides.
Performance: Deep cloning is expensive for large objects. Cache clones if reused:
private static array $clones = [];
public static function cloneIfCached(): static
{
return self::$clones[self::class] ??= clone new static(...);
}
How can I help you explore Laravel packages today?