1# DeepCopy 2 3DeepCopy helps you create deep copies (clones) of your objects. It is designed to handle cycles in the association graph. 4 5[](https://packagist.org/packages/myclabs/deep-copy) 6[](https://github.com/myclabs/DeepCopy/actions/workflows/ci.yaml) 7 8## Table of Contents 9 101. [How](#how) 111. [Why](#why) 12 1. [Using simply `clone`](#using-simply-clone) 13 1. [Overriding `__clone()`](#overriding-__clone) 14 1. [With `DeepCopy`](#with-deepcopy) 151. [How it works](#how-it-works) 161. [Going further](#going-further) 17 1. [Matchers](#matchers) 18 1. [Property name](#property-name) 19 1. [Specific property](#specific-property) 20 1. [Type](#type) 21 1. [Filters](#filters) 22 1. [`SetNullFilter`](#setnullfilter-filter) 23 1. [`KeepFilter`](#keepfilter-filter) 24 1. [`DoctrineCollectionFilter`](#doctrinecollectionfilter-filter) 25 1. [`DoctrineEmptyCollectionFilter`](#doctrineemptycollectionfilter-filter) 26 1. [`DoctrineProxyFilter`](#doctrineproxyfilter-filter) 27 1. [`ReplaceFilter`](#replacefilter-type-filter) 28 1. [`ShallowCopyFilter`](#shallowcopyfilter-type-filter) 291. [Edge cases](#edge-cases) 301. [Contributing](#contributing) 31 1. [Tests](#tests) 32 33 34## How? 35 36Install with Composer: 37 38``` 39composer require myclabs/deep-copy 40``` 41 42Use it: 43 44```php 45use DeepCopy\DeepCopy; 46 47$copier = new DeepCopy(); 48$myCopy = $copier->copy($myObject); 49``` 50 51 52## Why? 53 54- How do you create copies of your objects? 55 56```php 57$myCopy = clone $myObject; 58``` 59 60- How do you create **deep** copies of your objects (i.e. copying also all the objects referenced in the properties)? 61 62You use [`__clone()`](http://www.php.net/manual/en/language.oop5.cloning.php#object.clone) and implement the behavior 63yourself. 64 65- But how do you handle **cycles** in the association graph? 66 67Now you're in for a big mess :( 68 69 70 71 72### Using simply `clone` 73 74 75 76 77### Overriding `__clone()` 78 79 80 81 82### With `DeepCopy` 83 84 85 86 87## How it works 88 89DeepCopy recursively traverses all the object's properties and clones them. To avoid cloning the same object twice it 90keeps a hash map of all instances and thus preserves the object graph. 91 92To use it: 93 94```php 95use function DeepCopy\deep_copy; 96 97$copy = deep_copy($var); 98``` 99 100Alternatively, you can create your own `DeepCopy` instance to configure it differently for example: 101 102```php 103use DeepCopy\DeepCopy; 104 105$copier = new DeepCopy(true); 106 107$copy = $copier->copy($var); 108``` 109 110You may want to roll your own deep copy function: 111 112```php 113namespace Acme; 114 115use DeepCopy\DeepCopy; 116 117function deep_copy($var) 118{ 119 static $copier = null; 120 121 if (null === $copier) { 122 $copier = new DeepCopy(true); 123 } 124 125 return $copier->copy($var); 126} 127``` 128 129 130## Going further 131 132You can add filters to customize the copy process. 133 134The method to add a filter is `DeepCopy\DeepCopy::addFilter($filter, $matcher)`, 135with `$filter` implementing `DeepCopy\Filter\Filter` 136and `$matcher` implementing `DeepCopy\Matcher\Matcher`. 137 138We provide some generic filters and matchers. 139 140 141### Matchers 142 143 - `DeepCopy\Matcher` applies on a object attribute. 144 - `DeepCopy\TypeMatcher` applies on any element found in graph, including array elements. 145 146 147#### Property name 148 149The `PropertyNameMatcher` will match a property by its name: 150 151```php 152use DeepCopy\Matcher\PropertyNameMatcher; 153 154// Will apply a filter to any property of any objects named "id" 155$matcher = new PropertyNameMatcher('id'); 156``` 157 158 159#### Specific property 160 161The `PropertyMatcher` will match a specific property of a specific class: 162 163```php 164use DeepCopy\Matcher\PropertyMatcher; 165 166// Will apply a filter to the property "id" of any objects of the class "MyClass" 167$matcher = new PropertyMatcher('MyClass', 'id'); 168``` 169 170 171#### Type 172 173The `TypeMatcher` will match any element by its type (instance of a class or any value that could be parameter of 174[gettype()](http://php.net/manual/en/function.gettype.php) function): 175 176```php 177use DeepCopy\TypeMatcher\TypeMatcher; 178 179// Will apply a filter to any object that is an instance of Doctrine\Common\Collections\Collection 180$matcher = new TypeMatcher('Doctrine\Common\Collections\Collection'); 181``` 182 183 184### Filters 185 186- `DeepCopy\Filter` applies a transformation to the object attribute matched by `DeepCopy\Matcher` 187- `DeepCopy\TypeFilter` applies a transformation to any element matched by `DeepCopy\TypeMatcher` 188 189By design, matching a filter will stop the chain of filters (i.e. the next ones will not be applied). 190Using the ([`ChainableFilter`](#chainablefilter-filter)) won't stop the chain of filters. 191 192 193#### `SetNullFilter` (filter) 194 195Let's say for example that you are copying a database record (or a Doctrine entity), so you want the copy not to have 196any ID: 197 198```php 199use DeepCopy\DeepCopy; 200use DeepCopy\Filter\SetNullFilter; 201use DeepCopy\Matcher\PropertyNameMatcher; 202 203$object = MyClass::load(123); 204echo $object->id; // 123 205 206$copier = new DeepCopy(); 207$copier->addFilter(new SetNullFilter(), new PropertyNameMatcher('id')); 208 209$copy = $copier->copy($object); 210 211echo $copy->id; // null 212``` 213 214 215#### `KeepFilter` (filter) 216 217If you want a property to remain untouched (for example, an association to an object): 218 219```php 220use DeepCopy\DeepCopy; 221use DeepCopy\Filter\KeepFilter; 222use DeepCopy\Matcher\PropertyMatcher; 223 224$copier = new DeepCopy(); 225$copier->addFilter(new KeepFilter(), new PropertyMatcher('MyClass', 'category')); 226 227$copy = $copier->copy($object); 228// $copy->category has not been touched 229``` 230 231 232#### `ChainableFilter` (filter) 233 234If you use cloning on proxy classes, you might want to apply two filters for: 2351. loading the data 2362. applying a transformation 237 238You can use the `ChainableFilter` as a decorator of the proxy loader filter, which won't stop the chain of filters (i.e. 239the next ones may be applied). 240 241 242```php 243use DeepCopy\DeepCopy; 244use DeepCopy\Filter\ChainableFilter; 245use DeepCopy\Filter\Doctrine\DoctrineProxyFilter; 246use DeepCopy\Filter\SetNullFilter; 247use DeepCopy\Matcher\Doctrine\DoctrineProxyMatcher; 248use DeepCopy\Matcher\PropertyNameMatcher; 249 250$copier = new DeepCopy(); 251$copier->addFilter(new ChainableFilter(new DoctrineProxyFilter()), new DoctrineProxyMatcher()); 252$copier->addFilter(new SetNullFilter(), new PropertyNameMatcher('id')); 253 254$copy = $copier->copy($object); 255 256echo $copy->id; // null 257``` 258 259 260#### `DoctrineCollectionFilter` (filter) 261 262If you use Doctrine and want to copy an entity, you will need to use the `DoctrineCollectionFilter`: 263 264```php 265use DeepCopy\DeepCopy; 266use DeepCopy\Filter\Doctrine\DoctrineCollectionFilter; 267use DeepCopy\Matcher\PropertyTypeMatcher; 268 269$copier = new DeepCopy(); 270$copier->addFilter(new DoctrineCollectionFilter(), new PropertyTypeMatcher('Doctrine\Common\Collections\Collection')); 271 272$copy = $copier->copy($object); 273``` 274 275 276#### `DoctrineEmptyCollectionFilter` (filter) 277 278If you use Doctrine and want to copy an entity who contains a `Collection` that you want to be reset, you can use the 279`DoctrineEmptyCollectionFilter` 280 281```php 282use DeepCopy\DeepCopy; 283use DeepCopy\Filter\Doctrine\DoctrineEmptyCollectionFilter; 284use DeepCopy\Matcher\PropertyMatcher; 285 286$copier = new DeepCopy(); 287$copier->addFilter(new DoctrineEmptyCollectionFilter(), new PropertyMatcher('MyClass', 'myProperty')); 288 289$copy = $copier->copy($object); 290 291// $copy->myProperty will return an empty collection 292``` 293 294 295#### `DoctrineProxyFilter` (filter) 296 297If you use Doctrine and use cloning on lazy loaded entities, you might encounter errors mentioning missing fields on a 298Doctrine proxy class (...\\\_\_CG\_\_\Proxy). 299You can use the `DoctrineProxyFilter` to load the actual entity behind the Doctrine proxy class. 300**Make sure, though, to put this as one of your very first filters in the filter chain so that the entity is loaded 301before other filters are applied!** 302We recommend to decorate the `DoctrineProxyFilter` with the `ChainableFilter` to allow applying other filters to the 303cloned lazy loaded entities. 304 305```php 306use DeepCopy\DeepCopy; 307use DeepCopy\Filter\Doctrine\DoctrineProxyFilter; 308use DeepCopy\Matcher\Doctrine\DoctrineProxyMatcher; 309 310$copier = new DeepCopy(); 311$copier->addFilter(new ChainableFilter(new DoctrineProxyFilter()), new DoctrineProxyMatcher()); 312 313$copy = $copier->copy($object); 314 315// $copy should now contain a clone of all entities, including those that were not yet fully loaded. 316``` 317 318 319#### `ReplaceFilter` (type filter) 320 3211. If you want to replace the value of a property: 322 323```php 324use DeepCopy\DeepCopy; 325use DeepCopy\Filter\ReplaceFilter; 326use DeepCopy\Matcher\PropertyMatcher; 327 328$copier = new DeepCopy(); 329$callback = function ($currentValue) { 330 return $currentValue . ' (copy)' 331}; 332$copier->addFilter(new ReplaceFilter($callback), new PropertyMatcher('MyClass', 'title')); 333 334$copy = $copier->copy($object); 335 336// $copy->title will contain the data returned by the callback, e.g. 'The title (copy)' 337``` 338 3392. If you want to replace whole element: 340 341```php 342use DeepCopy\DeepCopy; 343use DeepCopy\TypeFilter\ReplaceFilter; 344use DeepCopy\TypeMatcher\TypeMatcher; 345 346$copier = new DeepCopy(); 347$callback = function (MyClass $myClass) { 348 return get_class($myClass); 349}; 350$copier->addTypeFilter(new ReplaceFilter($callback), new TypeMatcher('MyClass')); 351 352$copy = $copier->copy([new MyClass, 'some string', new MyClass]); 353 354// $copy will contain ['MyClass', 'some string', 'MyClass'] 355``` 356 357 358The `$callback` parameter of the `ReplaceFilter` constructor accepts any PHP callable. 359 360 361#### `ShallowCopyFilter` (type filter) 362 363Stop *DeepCopy* from recursively copying element, using standard `clone` instead: 364 365```php 366use DeepCopy\DeepCopy; 367use DeepCopy\TypeFilter\ShallowCopyFilter; 368use DeepCopy\TypeMatcher\TypeMatcher; 369use Mockery as m; 370 371$this->deepCopy = new DeepCopy(); 372$this->deepCopy->addTypeFilter( 373 new ShallowCopyFilter, 374 new TypeMatcher(m\MockInterface::class) 375); 376 377$myServiceWithMocks = new MyService(m::mock(MyDependency1::class), m::mock(MyDependency2::class)); 378// All mocks will be just cloned, not deep copied 379``` 380 381 382## Edge cases 383 384The following structures cannot be deep-copied with PHP Reflection. As a result they are shallow cloned and filters are 385not applied. There is two ways for you to handle them: 386 387- Implement your own `__clone()` method 388- Use a filter with a type matcher 389 390 391## Contributing 392 393DeepCopy is distributed under the MIT license. 394 395 396### Tests 397 398Running the tests is simple: 399 400```php 401vendor/bin/phpunit 402``` 403 404### Support 405 406Get professional support via [the Tidelift Subscription](https://tidelift.com/subscription/pkg/packagist-myclabs-deep-copy?utm_source=packagist-myclabs-deep-copy&utm_medium=referral&utm_campaign=readme). 407