1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace DeepCopyTest; |
4
|
|
|
|
5
|
|
|
use DateInterval; |
6
|
|
|
use DateTime; |
7
|
|
|
use DateTimeImmutable; |
8
|
|
|
use DateTimeZone; |
9
|
|
|
use DeepCopy\DeepCopy; |
10
|
|
|
use DeepCopy\Exception\CloneException; |
11
|
|
|
use DeepCopy\f001; |
12
|
|
|
use DeepCopy\f002; |
13
|
|
|
use DeepCopy\f003; |
14
|
|
|
use DeepCopy\f004; |
15
|
|
|
use DeepCopy\f005; |
16
|
|
|
use DeepCopy\f006; |
17
|
|
|
use DeepCopy\f007; |
18
|
|
|
use DeepCopy\f008; |
19
|
|
|
use DeepCopy\Filter\KeepFilter; |
20
|
|
|
use DeepCopy\Filter\SetNullFilter; |
21
|
|
|
use DeepCopy\Matcher\PropertyNameMatcher; |
22
|
|
|
use DeepCopy\Matcher\PropertyTypeMatcher; |
23
|
|
|
use DeepCopy\TypeFilter\ShallowCopyFilter; |
24
|
|
|
use DeepCopy\TypeMatcher\TypeMatcher; |
25
|
|
|
use PHPUnit_Framework_TestCase; |
26
|
|
|
use SplDoublyLinkedList; |
27
|
|
|
use stdClass; |
28
|
|
|
use function DeepCopy\deep_copy; |
29
|
|
|
|
30
|
|
|
/** |
31
|
|
|
* @covers \DeepCopy\DeepCopy |
32
|
|
|
*/ |
33
|
|
|
class DeepCopyTest extends PHPUnit_Framework_TestCase |
34
|
|
|
{ |
35
|
|
|
/** |
36
|
|
|
* @dataProvider provideScalarValues |
37
|
|
|
*/ |
38
|
|
|
public function test_it_can_copy_scalar_values($value) |
39
|
|
|
{ |
40
|
|
|
$copy = deep_copy($value); |
41
|
|
|
|
42
|
|
|
$this->assertSame($value, $copy); |
43
|
|
|
} |
44
|
|
|
|
45
|
|
|
public function provideScalarValues() |
46
|
|
|
{ |
47
|
|
|
return [ |
48
|
|
|
[true], |
49
|
|
|
['string'], |
50
|
|
|
[null], |
51
|
|
|
[10], |
52
|
|
|
[-1], |
53
|
|
|
[.5], |
54
|
|
|
]; |
55
|
|
|
} |
56
|
|
|
|
57
|
|
|
public function test_it_can_copy_an_array_of_scalar_values() |
58
|
|
|
{ |
59
|
|
|
$copy = deep_copy([10, 20]); |
60
|
|
|
|
61
|
|
|
$this->assertSame([10, 20], $copy); |
62
|
|
|
} |
63
|
|
|
|
64
|
|
|
public function test_it_can_copy_an_object() |
65
|
|
|
{ |
66
|
|
|
$object = new stdClass(); |
67
|
|
|
|
68
|
|
|
$copy = deep_copy($object); |
69
|
|
|
|
70
|
|
|
$this->assertEqualButNotSame($object, $copy); |
71
|
|
|
} |
72
|
|
|
|
73
|
|
|
public function test_it_can_copy_an_array_of_objects() |
74
|
|
|
{ |
75
|
|
|
$object = [new stdClass()]; |
76
|
|
|
|
77
|
|
|
$copy = deep_copy($object); |
78
|
|
|
|
79
|
|
|
$this->assertEqualButNotSame($object, $copy); |
80
|
|
|
$this->assertEqualButNotSame($object[0], $copy[0]); |
81
|
|
|
} |
82
|
|
|
|
83
|
|
|
/** |
84
|
|
|
* @dataProvider provideObjectWithScalarValues |
85
|
|
|
*/ |
86
|
|
|
public function test_it_can_copy_an_object_with_scalar_properties($object, $expectedVal) |
87
|
|
|
{ |
88
|
|
|
$copy = deep_copy($object); |
89
|
|
|
|
90
|
|
|
$this->assertEqualButNotSame($object, $copy); |
91
|
|
|
$this->assertSame($expectedVal, $copy->prop); |
92
|
|
|
} |
93
|
|
|
|
94
|
|
|
public function provideObjectWithScalarValues() |
95
|
|
|
{ |
96
|
|
|
$createObject = function ($val) { |
97
|
|
|
$object = new stdClass(); |
98
|
|
|
|
99
|
|
|
$object->prop = $val; |
100
|
|
|
|
101
|
|
|
return $object; |
102
|
|
|
}; |
103
|
|
|
|
104
|
|
|
return array_map( |
105
|
|
|
function (array $vals) use ($createObject) { |
106
|
|
|
return [$createObject($vals[0]), $vals[0]]; |
107
|
|
|
}, |
108
|
|
|
$this->provideScalarValues() |
109
|
|
|
); |
110
|
|
|
} |
111
|
|
|
|
112
|
|
|
public function test_it_can_copy_an_object_with_an_object_property() |
113
|
|
|
{ |
114
|
|
|
$foo = new stdClass(); |
115
|
|
|
$bar = new stdClass(); |
116
|
|
|
|
117
|
|
|
$foo->bar = $bar; |
118
|
|
|
|
119
|
|
|
$copy = deep_copy($foo); |
120
|
|
|
|
121
|
|
|
$this->assertEqualButNotSame($foo, $copy); |
122
|
|
|
$this->assertEqualButNotSame($foo->bar, $copy->bar); |
123
|
|
|
} |
124
|
|
|
|
125
|
|
|
public function test_it_copies_dynamic_properties() |
126
|
|
|
{ |
127
|
|
|
$foo = new stdClass(); |
128
|
|
|
$bar = new stdClass(); |
129
|
|
|
|
130
|
|
|
$foo->bar = $bar; |
131
|
|
|
|
132
|
|
|
$copy = deep_copy($foo); |
133
|
|
|
|
134
|
|
|
$this->assertEqualButNotSame($foo, $copy); |
135
|
|
|
$this->assertEqualButNotSame($foo->bar, $copy->bar); |
136
|
|
|
} |
137
|
|
|
|
138
|
|
|
/** |
139
|
|
|
* @ticket https://github.com/myclabs/DeepCopy/issues/38 |
140
|
|
|
* @ticket https://github.com/myclabs/DeepCopy/pull/70 |
141
|
|
|
* @ticket https://github.com/myclabs/DeepCopy/pull/76 |
142
|
|
|
*/ |
143
|
|
|
public function test_it_can_copy_an_object_with_a_date_object_property() |
144
|
|
|
{ |
145
|
|
|
$object = new stdClass(); |
146
|
|
|
|
147
|
|
|
$object->d1 = new DateTime(); |
148
|
|
|
$object->d2 = new DateTimeImmutable(); |
149
|
|
|
$object->dtz = new DateTimeZone('UTC'); |
150
|
|
|
$object->di = new DateInterval('P2D'); |
151
|
|
|
|
152
|
|
|
$copy = deep_copy($object); |
153
|
|
|
|
154
|
|
|
$this->assertEqualButNotSame($object->d1, $copy->d1); |
155
|
|
|
$this->assertEqualButNotSame($object->d2, $copy->d2); |
156
|
|
|
$this->assertEqualButNotSame($object->dtz, $copy->dtz); |
157
|
|
|
$this->assertEqualButNotSame($object->di, $copy->di); |
158
|
|
|
} |
159
|
|
|
|
160
|
|
|
/** |
161
|
|
|
* @ticket https://github.com/myclabs/DeepCopy/pull/70 |
162
|
|
|
*/ |
163
|
|
|
public function test_it_skips_the_copy_for_userland_datetimezone() |
164
|
|
|
{ |
165
|
|
|
$deepCopy = new DeepCopy(); |
166
|
|
|
$deepCopy->addFilter( |
167
|
|
|
new SetNullFilter(), |
168
|
|
|
new PropertyNameMatcher('cloned') |
169
|
|
|
); |
170
|
|
|
|
171
|
|
|
$object = new stdClass(); |
172
|
|
|
|
173
|
|
|
$object->dtz = new f007\FooDateTimeZone('UTC'); |
174
|
|
|
|
175
|
|
|
$copy = $deepCopy->copy($object); |
176
|
|
|
|
177
|
|
|
$this->assertTrue($copy->dtz->cloned); |
178
|
|
|
} |
179
|
|
|
|
180
|
|
|
/** |
181
|
|
|
* @ticket https://github.com/myclabs/DeepCopy/pull/76 |
182
|
|
|
*/ |
183
|
|
|
public function test_it_skips_the_copy_for_userland_dateinterval() |
184
|
|
|
{ |
185
|
|
|
$deepCopy = new DeepCopy(); |
186
|
|
|
$deepCopy->addFilter( |
187
|
|
|
new SetNullFilter(), |
188
|
|
|
new PropertyNameMatcher('cloned') |
189
|
|
|
); |
190
|
|
|
|
191
|
|
|
$object = new stdClass(); |
192
|
|
|
|
193
|
|
|
$object->di = new f007\FooDateInterval('P2D'); |
194
|
|
|
|
195
|
|
|
$copy = $deepCopy->copy($object); |
196
|
|
|
|
197
|
|
|
$this->assertFalse($copy->di->cloned); |
198
|
|
|
} |
199
|
|
|
|
200
|
|
|
public function test_it_copies_the_private_properties_of_the_parent_class() |
201
|
|
|
{ |
202
|
|
|
$object = new f001\B(); |
203
|
|
|
|
204
|
|
|
$object->setAProp($aStdClass = new stdClass()); |
205
|
|
|
$object->setBProp($bStdClass = new stdClass()); |
206
|
|
|
|
207
|
|
|
/** @var f001\B $copy */ |
208
|
|
|
$copy = deep_copy($object); |
209
|
|
|
|
210
|
|
|
$this->assertEqualButNotSame($aStdClass, $copy->getAProp()); |
211
|
|
|
$this->assertEqualButNotSame($bStdClass, $copy->getBProp()); |
212
|
|
|
} |
213
|
|
|
|
214
|
|
|
public function test_it_keeps_reference_of_the_copied_objects_when_copying_the_graph() |
215
|
|
|
{ |
216
|
|
|
$a = new f002\A(); |
217
|
|
|
|
218
|
|
|
$b = new stdClass(); |
219
|
|
|
$c = new stdClass(); |
220
|
|
|
|
221
|
|
|
$a->setProp1($b); |
222
|
|
|
$a->setProp2($c); |
223
|
|
|
|
224
|
|
|
$b->c = $c; |
225
|
|
|
|
226
|
|
|
/** @var f002\A $copy */ |
227
|
|
|
$copy = deep_copy($a); |
228
|
|
|
|
229
|
|
|
$this->assertEqualButNotSame($a, $copy); |
230
|
|
|
$this->assertEqualButNotSame($b, $copy->getProp1()); |
231
|
|
|
$this->assertEqualButNotSame($c, $copy->getProp2()); |
232
|
|
|
|
233
|
|
|
$this->assertSame($copy->getProp1()->c, $copy->getProp2()); |
234
|
|
|
} |
235
|
|
|
|
236
|
|
|
public function test_it_can_copy_graphs_with_circular_references() |
237
|
|
|
{ |
238
|
|
|
$a = new stdClass(); |
239
|
|
|
$b = new stdClass(); |
240
|
|
|
|
241
|
|
|
$a->prop = $b; |
242
|
|
|
$b->prop = $a; |
243
|
|
|
|
244
|
|
|
$copy = deep_copy($a); |
245
|
|
|
|
246
|
|
|
$this->assertEqualButNotSame($a, $copy); |
247
|
|
|
$this->assertEqualButNotSame($b, $copy->prop); |
248
|
|
|
|
249
|
|
|
$this->assertSame($copy, $copy->prop->prop); |
250
|
|
|
} |
251
|
|
|
|
252
|
|
|
public function test_it_can_copy_graphs_with_circular_references_with_userland_class() |
253
|
|
|
{ |
254
|
|
|
$a = new f003\Foo('a'); |
255
|
|
|
$b = new f003\Foo('b'); |
256
|
|
|
|
257
|
|
|
$a->setProp($b); |
258
|
|
|
$b->setProp($a); |
259
|
|
|
|
260
|
|
|
/** @var f003\Foo $copy */ |
261
|
|
|
$copy = deep_copy($a); |
262
|
|
|
|
263
|
|
|
$this->assertEqualButNotSame($a, $copy); |
264
|
|
|
$this->assertEqualButNotSame($b, $copy->getProp()); |
265
|
|
|
|
266
|
|
|
$this->assertSame($copy, $copy->getProp()->getProp()); |
267
|
|
|
} |
268
|
|
|
|
269
|
|
|
public function test_it_cannot_copy_unclonable_items() |
270
|
|
|
{ |
271
|
|
|
$object = new f004\UnclonableItem(); |
272
|
|
|
|
273
|
|
|
try { |
274
|
|
|
deep_copy($object); |
275
|
|
|
|
276
|
|
|
$this->fail('Expected exception to be thrown.'); |
277
|
|
|
} catch (CloneException $exception) { |
278
|
|
|
$this->assertSame( |
279
|
|
|
sprintf( |
280
|
|
|
'The class "%s" is not cloneable.', |
281
|
|
|
f004\UnclonableItem::class |
282
|
|
|
), |
283
|
|
|
$exception->getMessage() |
284
|
|
|
); |
285
|
|
|
$this->assertSame(0, $exception->getCode()); |
286
|
|
|
$this->assertNull($exception->getPrevious()); |
287
|
|
|
} |
288
|
|
|
} |
289
|
|
|
|
290
|
|
|
public function test_it_can_skip_uncloneable_objects() |
291
|
|
|
{ |
292
|
|
|
$object = new f004\UnclonableItem(); |
293
|
|
|
|
294
|
|
|
$deepCopy = new DeepCopy(); |
295
|
|
|
$deepCopy->skipUncloneable(true); |
296
|
|
|
|
297
|
|
|
$copy = $deepCopy->copy($object); |
298
|
|
|
|
299
|
|
|
$this->assertSame($object, $copy); |
300
|
|
|
} |
301
|
|
|
|
302
|
|
|
public function test_it_uses_the_userland_defined_cloned_method() |
303
|
|
|
{ |
304
|
|
|
$object = new f005\Foo(); |
305
|
|
|
|
306
|
|
|
$copy = deep_copy($object); |
307
|
|
|
|
308
|
|
|
$this->assertTrue($copy->cloned); |
309
|
|
|
} |
310
|
|
|
|
311
|
|
|
public function test_it_only_uses_the_userland_defined_cloned_method_when_configured_to_do_so() |
312
|
|
|
{ |
313
|
|
|
$object = new f005\Foo(); |
314
|
|
|
$object->foo = new stdClass(); |
|
|
|
|
315
|
|
|
|
316
|
|
|
$copy = deep_copy($object, true); |
317
|
|
|
|
318
|
|
|
$this->assertTrue($copy->cloned); |
319
|
|
|
$this->assertSame($object->foo, $copy->foo); |
320
|
|
|
} |
321
|
|
|
|
322
|
|
|
public function test_it_uses_type_filter_to_copy_objects_if_matcher_matches() |
323
|
|
|
{ |
324
|
|
|
$deepCopy = new DeepCopy(); |
325
|
|
|
$deepCopy->addTypeFilter( |
326
|
|
|
new ShallowCopyFilter(), |
327
|
|
|
new TypeMatcher(f006\A::class) |
328
|
|
|
); |
329
|
|
|
|
330
|
|
|
$a = new f006\A; |
331
|
|
|
$b = new f006\B; |
332
|
|
|
|
333
|
|
|
$a->setAProp($b); |
334
|
|
|
|
335
|
|
|
/** @var f006\A $copy */ |
336
|
|
|
$copy = $deepCopy->copy($a); |
337
|
|
|
|
338
|
|
|
$this->assertTrue($copy->cloned); |
339
|
|
|
$this->assertFalse($copy->getAProp()->cloned); |
340
|
|
|
$this->assertSame($b, $copy->getAProp()); |
341
|
|
|
} |
342
|
|
|
|
343
|
|
|
public function test_it_uses_filters_to_copy_object_properties_if_matcher_matches() |
344
|
|
|
{ |
345
|
|
|
$deepCopy = new DeepCopy(); |
346
|
|
|
$deepCopy->addFilter( |
347
|
|
|
new SetNullFilter(), |
348
|
|
|
new PropertyNameMatcher('cloned') |
349
|
|
|
); |
350
|
|
|
|
351
|
|
|
$a = new f006\A; |
352
|
|
|
$b = new f006\B; |
353
|
|
|
|
354
|
|
|
$a->setAProp($b); |
355
|
|
|
|
356
|
|
|
/** @var f006\A $copy */ |
357
|
|
|
$copy = $deepCopy->copy($a); |
358
|
|
|
|
359
|
|
|
$this->assertNull($copy->cloned); |
360
|
|
|
$this->assertNull($copy->getAProp()->cloned); |
361
|
|
|
} |
362
|
|
|
|
363
|
|
|
public function test_it_uses_the_first_filter_matching_for_copying_object_properties() |
364
|
|
|
{ |
365
|
|
|
$deepCopy = new DeepCopy(); |
366
|
|
|
$deepCopy->addFilter( |
367
|
|
|
new SetNullFilter(), |
368
|
|
|
new PropertyNameMatcher('cloned') |
369
|
|
|
); |
370
|
|
|
$deepCopy->addFilter( |
371
|
|
|
new KeepFilter(), |
372
|
|
|
new PropertyNameMatcher('cloned') |
373
|
|
|
); |
374
|
|
|
|
375
|
|
|
$a = new f006\A; |
376
|
|
|
$b = new f006\B; |
377
|
|
|
|
378
|
|
|
$a->setAProp($b); |
379
|
|
|
|
380
|
|
|
/** @var f006\A $copy */ |
381
|
|
|
$copy = $deepCopy->copy($a); |
382
|
|
|
|
383
|
|
|
$this->assertNull($copy->cloned); |
384
|
|
|
$this->assertNull($copy->getAProp()->cloned); |
385
|
|
|
} |
386
|
|
|
|
387
|
|
|
/** |
388
|
|
|
* @ticket https://github.com/myclabs/DeepCopy/pull/49 |
389
|
|
|
*/ |
390
|
|
|
public function test_it_can_copy_a_SplDoublyLinkedList() |
391
|
|
|
{ |
392
|
|
|
$object = new SplDoublyLinkedList(); |
393
|
|
|
|
394
|
|
|
$a = new stdClass(); |
395
|
|
|
$b = new stdClass(); |
396
|
|
|
|
397
|
|
|
$a->b = $b; |
398
|
|
|
|
399
|
|
|
$object->push($a); |
400
|
|
|
|
401
|
|
|
/** @var SplDoublyLinkedList $copy */ |
402
|
|
|
$copy = deep_copy($object); |
403
|
|
|
|
404
|
|
|
$this->assertEqualButNotSame($object, $copy); |
405
|
|
|
|
406
|
|
|
$aCopy = $copy->pop(); |
407
|
|
|
|
408
|
|
|
$this->assertEqualButNotSame($b, $aCopy->b); |
409
|
|
|
} |
410
|
|
|
|
411
|
|
|
/** |
412
|
|
|
* @ticket https://github.com/myclabs/DeepCopy/issues/62 |
413
|
|
|
*/ |
414
|
|
|
public function test_matchers_can_access_to_parent_private_properties() |
415
|
|
|
{ |
416
|
|
|
$deepCopy = new DeepCopy(); |
417
|
|
|
$deepCopy->addFilter(new SetNullFilter(), new PropertyTypeMatcher(stdClass::class)); |
418
|
|
|
|
419
|
|
|
$object = new f008\B(new stdClass()); |
420
|
|
|
|
421
|
|
|
/** @var f008\B $copy */ |
422
|
|
|
$copy = $deepCopy->copy($object); |
423
|
|
|
|
424
|
|
|
$this->assertNull($copy->getFoo()); |
425
|
|
|
} |
426
|
|
|
|
427
|
|
|
private function assertEqualButNotSame($expected, $val) |
428
|
|
|
{ |
429
|
|
|
$this->assertEquals($expected, $val); |
430
|
|
|
$this->assertNotSame($expected, $val); |
431
|
|
|
} |
432
|
|
|
} |
433
|
|
|
|
An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.
If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.