annotate vendor/myclabs/deep-copy/README.md @ 5:12f9dff5fda9 tip

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