File: README.md

package info (click to toggle)
php-deepcopy 1.5.5-1
  • links: PTS, VCS
  • area: main
  • in suites: stretch
  • size: 220 kB
  • ctags: 97
  • sloc: php: 343; makefile: 8
file content (271 lines) | stat: -rw-r--r-- 7,923 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
# DeepCopy

DeepCopy helps you create deep copies (clones) of your objects. It is designed to handle cycles in the association graph.

[![Build Status](https://travis-ci.org/myclabs/DeepCopy.png?branch=master)](https://travis-ci.org/myclabs/DeepCopy) [![Coverage Status](https://coveralls.io/repos/myclabs/DeepCopy/badge.png?branch=master)](https://coveralls.io/r/myclabs/DeepCopy?branch=master) [![Scrutinizer Quality Score](https://scrutinizer-ci.com/g/myclabs/DeepCopy/badges/quality-score.png?s=2747100c19b275f93a777e3297c6c12d1b68b934)](https://scrutinizer-ci.com/g/myclabs/DeepCopy/)
[![Total Downloads](https://poser.pugx.org/myclabs/deep-copy/downloads.svg)](https://packagist.org/packages/myclabs/deep-copy)


## Table of Contents

1. [How](#how)
1. [Why](#why)
    1. [Using simply `clone`](#using-simply-clone)
    1. [Overridding `__clone()`](#overridding-__clone)
    1. [With `DeepCopy`](#with-deepcopy)
1. [How it works](#how-it-works)
1. [Going further](#going-further)
    1. [Matchers](#matchers)
        1. [Property name](#property-name)
        1. [Specific property](#specific-property)
        1. [Type](#type)
    1. [Filters](#filters)
        1. [`SetNullFilter`](#setnullfilter)
        1. [`KeepFilter`](#keepfilter)
        1. [`ReplaceFilter`](#replacefilter)
        1. [`ShallowCopyFilter`](#doctrinecollectionfilter)
        1. [`DoctrineCollectionFilter`](#doctrinecollectionfilter)
        1. [`DoctrineEmptyCollectionFilter`](#doctrineemptycollectionfilter)
1. [Contributing](#contributing)
    1. [Tests](#tests)

## How?

Install with Composer:

```json
composer require myclabs/deep-copy
```

Use simply:

```php
use DeepCopy\DeepCopy;

$deepCopy = new DeepCopy();
$myCopy = $deepCopy->copy($myObject);
```


## Why?

- How do you create copies of your objects?

```php
$myCopy = clone $myObject;
```

- How do you create **deep** copies of your objects (i.e. copying also all the objects referenced in the properties)?

You use [`__clone()`](http://www.php.net/manual/en/language.oop5.cloning.php#object.clone) and implement the behavior yourself.

- But how do you handle **cycles** in the association graph?

Now you're in for a big mess :(

![association graph](doc/graph.png)

### Using simply `clone`

![Using clone](doc/clone.png)

### Overridding `__clone()`

![Overridding __clone](doc/deep-clone.png)

### With `DeepCopy`

![With DeepCopy](doc/deep-copy.png)

## How it works

DeepCopy recursively traverses all the object's properties and clones them. To avoid cloning the same object twice it keeps a hash map of all instances and thus preserves the object graph.

## Going further

You can add filters to customize the copy process.

The method to add a filter is `$deepCopy->addFilter($filter, $matcher)`,
with `$filter` implementing `DeepCopy\Filter\Filter`
and `$matcher` implementing `DeepCopy\Matcher\Matcher`.

We provide some generic filters and matchers.

### Matchers

  - `DeepCopy\Matcher` applies on a object attribute.
  - `DeepCopy\TypeMatcher` applies on any element found in graph, including array elements.

#### Property name

The `PropertyNameMatcher` will match a property by its name:

```php
use DeepCopy\Matcher\PropertyNameMatcher;

$matcher = new PropertyNameMatcher('id');
// will apply a filter to any property of any objects named "id"
```

#### Specific property

The `PropertyMatcher` will match a specific property of a specific class:

```php
use DeepCopy\Matcher\PropertyMatcher;

$matcher = new PropertyMatcher('MyClass', 'id');
// will apply a filter to the property "id" of any objects of the class "MyClass"
```

#### Type

The `TypeMatcher` will match any element by its type (instance of a class or any value that could be parameter of [gettype()](http://php.net/manual/en/function.gettype.php) function):

```php
use DeepCopy\TypeMatcher\TypeMatcher;

$matcher = new TypeMatcher('Doctrine\Common\Collections\Collection');
// will apply a filter to any object that is an instance of Doctrine\Common\Collections\Collection
```

### Filters

  - `DeepCopy\Filter` applies a transformation to the object attribute matched by `DeepCopy\Matcher`.
  - `DeepCopy\TypeFilter` applies a transformation to any element matched by `DeepCopy\TypeMatcher`.

#### `SetNullFilter`

Let's say for example that you are copying a database record (or a Doctrine entity), so you want the copy not to have any ID:

```php
use DeepCopy\DeepCopy;
use DeepCopy\Filter\SetNullFilter;
use DeepCopy\Matcher\PropertyNameMatcher;

$myObject = MyClass::load(123);
echo $myObject->id; // 123

$deepCopy = new DeepCopy();
$deepCopy->addFilter(new SetNullFilter(), new PropertyNameMatcher('id'));
$myCopy = $deepCopy->copy($myObject);

echo $myCopy->id; // null
```

#### `KeepFilter`

If you want a property to remain untouched (for example, an association to an object):

```php
use DeepCopy\DeepCopy;
use DeepCopy\Filter\KeepFilter;
use DeepCopy\Matcher\PropertyMatcher;

$deepCopy = new DeepCopy();
$deepCopy->addFilter(new KeepFilter(), new PropertyMatcher('MyClass', 'category'));
$myCopy = $deepCopy->copy($myObject);

// $myCopy->category has not been touched
```

#### `ReplaceFilter`

  1. If you want to replace the value of a property:

  ```php
  use DeepCopy\DeepCopy;
  use DeepCopy\Filter\ReplaceFilter;
  use DeepCopy\Matcher\PropertyMatcher;

  $deepCopy = new DeepCopy();
  $callback = function ($currentValue) {
      return $currentValue . ' (copy)'
  };
  $deepCopy->addFilter(new ReplaceFilter($callback), new PropertyMatcher('MyClass', 'title'));
  $myCopy = $deepCopy->copy($myObject);

  // $myCopy->title will contain the data returned by the callback, e.g. 'The title (copy)'
  ```

  2. If you want to replace whole element:

  ```php
  use DeepCopy\DeepCopy;
  use DeepCopy\TypeFilter\ReplaceFilter;
  use DeepCopy\TypeMatcher\TypeMatcher;

  $deepCopy = new DeepCopy();
  $callback = function (MyClass $myClass) {
      return get_class($myClass);
  };
  $deepCopy->addTypeFilter(new ReplaceFilter($callback), new TypeMatcher('MyClass'));
  $myCopy = $deepCopy->copy(array(new MyClass, 'some string', new MyClass));

  // $myCopy will contain ['MyClass', 'some stirng', 'MyClass']
  ```


The `$callback` parameter of the `ReplaceFilter` constructor accepts any PHP callable.

#### `ShallowCopyFilter`

Stop *DeepCopy* from recursively copying element, using standard `clone` instead:

```php
use DeepCopy\DeepCopy;
use DeepCopy\TypeFilter\ShallowCopyFilter;
use DeepCopy\TypeMatcher\TypeMatcher;
use Mockery as m;

$this->deepCopy = new DeepCopy();
$this->deepCopy->addTypeFilter(
	new ShallowCopyFilter,
	new TypeMatcher(m\MockInterface::class)
);

$myServiceWithMocks = new MyService(m::mock(MyDependency1::class), m::mock(MyDependency2::class));
// all mocks will be just cloned, not deep-copied
```

#### `DoctrineCollectionFilter`

If you use Doctrine and want to copy an entity, you will need to use the `DoctrineCollectionFilter`:

```php
use DeepCopy\DeepCopy;
use DeepCopy\Filter\Doctrine\DoctrineCollectionFilter;
use DeepCopy\Matcher\PropertyTypeMatcher;

$deepCopy = new DeepCopy();
$deepCopy->addFilter(new DoctrineCollectionFilter(), new PropertyTypeMatcher('Doctrine\Common\Collections\Collection'));
$myCopy = $deepCopy->copy($myObject);
```

#### `DoctrineEmptyCollectionFilter`

If you use Doctrine and want to copy an entity who contains a `Collection` that you want to be reset, you can use the `DoctrineEmptyCollectionFilter`

```php
use DeepCopy\DeepCopy;
use DeepCopy\Filter\Doctrine\DoctrineEmptyCollectionFilter;
use DeepCopy\Matcher\PropertyMatcher;

$deepCopy = new DeepCopy();
$deepCopy->addFilter(new DoctrineEmptyCollectionFilter(), new PropertyMatcher('MyClass', 'myProperty'));
$myCopy = $deepCopy->copy($myObject);

// $myCopy->myProperty will return an empty collection
```

## Contributing

DeepCopy is distributed under the MIT license.

### Tests

Running the tests is simple:

```php
phpunit
```