eboreum / cloner
Immutably clone (copy) objects and override their properties. Works for readonly classes and properties as well.
Requires
- php: ^8.5
- eboreum/caster: ^3.0
- eboreum/immutable-interface: ^1.0
Requires (Dev)
- overtrue/phplint: ^9.7
- phpstan/phpstan: ^2.1
- phpunit/phpunit: ^13.1
- slevomat/coding-standard: ^8.27
- squizlabs/php_codesniffer: ^4.0
README

Immutably clone (copy) objects and override their properties. Works for readonly classes and properties as well.
Optionally, add custom logic in a __clone method in your own class to apply regular cloning logic. The logic in the __clone method is executed after values have been copied to a clone.
Requirements
"php": "^8.5",
"eboreum/caster": "^3.0",
"eboreum/immutable-interface": "^1.0"
Installation
Via Composer (https://packagist.org/packages/eboreum/cloner):
composer require eboreum/cloner
Via GitLab:
git clone git@gitlab.com:eboreum/cloner.git
Simple cloning
If all you need is a simple clone wrapper, without the need for changing properties, you may use Eboreum\Cloner\SimpleCloner instead. Using SimpleCloner rather than the native clone operator (conventional Object Cloning; clone $object) can make unit testing easier because the cloner can be mocked or stubbed in PHPUnit. This allows tests to verify that cloning was requested and to control which object is returned, capabilities that are not available when using the clone language construct directly.
Corner cases: PHP internal classes
PHP has a series of internal classes, including DateTimeImmutable and SplFileObject, to name a few. Some of these internal classes break with conventional object state behaviors. Here are three examples:
DateTimeImmutable
Exposes debugger-visible date/time fields, but its real state is nativetimelibdata: timestamp, timezone object, parsed calendar fields, relative info, etc. If created withReflectionClass::newInstanceWithoutConstructor(), that native state is missing, so methods likeformat()can fail with "not correctly initialized”" Reflection explicitly allows constructor bypassing, but internal final classes may reject it.SplFileObject
It may show no meaningful PHP properties, but it wraps a native file handle plus cursor state: path, mode, EOF status, current line, flags, CSV controls, seek position, and stream metadata. The manual describes it as an object-oriented interface to a file, not as a property-backed value object.SimpleXMLElement
This looks like a PHP object with dynamic-looking child/attribute access, but it is backed bylibxmlnode/document state.print_r()/property access can make it feel property-based, yet its actual identity is a pointer into an XML tree plus namespace/XPath/iterator context. The manual lists methods such aschildren(),attributes(),xpath(), and iterator methods, showing the state lives in the XML node model rather than normal PHP properties.
So these are "objects" at the PHP surface, but their invariants live in C-level structures. Reflection can sometimes create the shell, but not the internal payload.
This library will, whenever possible, restore the state of these objects. If this is not possible, a Eboreum\Cloner\Exception\CannotRestoreObjectException is thrown.
Realistically, you will only really encounter these corner cases when you have objects that extend the internal PHP classes. Or classes added by some PHP extension, PECL or otherwise, where state information is not stored in class properties.
Alternatives
myclabs/deep-copy: The nuclear option.spatie/php-cloneable: Has uncovered edge cases. See reasoning further down.
License
See LICENSE file. Basically: Use this library at your own risk.
Contributing
We prefer that you create an issue and or a merge request at https://gitlab.com/eboreum/cloner, and have a discussion about a feature or bug here.
Credits
Authors
- Kasper Søfren (kafoso)
E-mail: soefritz@gmail.com
Homepage: https://gitlab.com/kafoso / https://github.com/kafoso
Acknowledgements
This implementation is inspired by https://packagist.org/packages/spatie/php-cloneable. However, there are six main differences to the Spatie implementation.
These six differences are:
- The "
continue" on line 26 seems like a bug in that it silently ignores unmatched keys in the$valuesargument. - The "
bindTo" call on 34 may cause aTypeError. This results in sequential error discovery (one at a time). - In
eboreum/cloner(this codebase), we walk parent classes to clone parent-scoped properties as well. Something thatspatie/php-cloneabledoes not do, which can lead to undesired behavior. - In
spatie/php-cloneable, dynamic properties are not transferred. - In
eboreum/cloner(this codebase),__cloneis called when an object defines it. - In
eboreum/cloner(this codebase), we will attempt to restore object state for PHP internal classes.
In the implementation in eboreum/cloner (this codebase):
- Instead of a single
TypeError(once per faulty property value), we aggregate all type problems and report all at once. - If a child class and a parent class both declare a property with the same name, only the first-discovered property may be overridden from $values; later parent-scoped properties with that name are copied from the original object.
- Dynamic properties are transferred, which mirrors the behavior of
clone $myObject. - The
__clonemethod is invoked using a closure bound to the method's declaring class.
AI Implementation Assistance Disclaimer
AI, GPT-5.5 specifically, was used in the implementation of this codebase. However, the code has been thoroughly reviewed by human eyes.