Completed
Push — master ( cd2604...399c1f )
by Matthieu
03:36
created

DeepCopyTest::testSplDoublyLinkedListDeepCopy()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 17
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 17
rs 9.4285
cc 1
eloc 12
nc 1
nop 0
1
<?php
2
3
namespace DeepCopyTest;
4
5
use DeepCopy\DeepCopy;
6
use DeepCopy\Filter\Filter;
7
use DeepCopy\Matcher\PropertyMatcher;
8
use DeepCopy\Matcher\PropertyTypeMatcher;
9
use DeepCopy\TypeFilter\Spl\SplDoublyLinkedList;
10
use DeepCopy\TypeFilter\Spl\SplStackFilter;
11
use DeepCopy\TypeFilter\TypeFilter;
12
use DeepCopy\TypeMatcher\TypeMatcher;
13
14
/**
15
 * DeepCopyTest
16
 */
17
class DeepCopyTest extends AbstractTestClass
18
{
19
    public function testSimpleObjectCopy()
20
    {
21
        $o = new A();
22
23
        $deepCopy = new DeepCopy();
24
25
        $this->assertDeepCopyOf($o, $deepCopy->copy($o));
26
    }
27
28
    public function testPropertyScalarCopy()
29
    {
30
        $o = new A();
31
        $o->property1 = 'foo';
32
33
        $deepCopy = new DeepCopy();
34
35
        $this->assertDeepCopyOf($o, $deepCopy->copy($o));
36
    }
37
38
    public function testPropertyObjectCopy()
39
    {
40
        $o = new A();
41
        $o->property1 = new B();
42
43
        $deepCopy = new DeepCopy();
44
45
        $this->assertDeepCopyOf($o, $deepCopy->copy($o));
46
    }
47
48
    public function testPropertyObjectCopyWithDateTimes()
49
    {
50
        $o = new A();
51
        $o->date1 = new \DateTime();
0 ignored issues
show
Bug introduced by Théo FIDRY
The property date1 does not seem to exist in DeepCopyTest\A.

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.

Loading history...
52
        if (class_exists('DateTimeImmutable')) {
53
            $o->date2 = new \DateTimeImmutable();
0 ignored issues
show
Bug introduced by Théo FIDRY
The property date2 does not seem to exist in DeepCopyTest\A.

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.

Loading history...
54
        }
55
56
        $deepCopy = new DeepCopy();
57
        $c = $deepCopy->copy($o);
58
59
        $this->assertDeepCopyOf($o, $c);
60
61
        $c->date1->setDate(2015, 01, 04);
62
        $this->assertNotEquals($c->date1, $o->date1);
63
    }
64
65
    public function testPrivatePropertyOfParentObjectCopy()
66
    {
67
        $o = new E();
68
        $o->setProperty1(new B);
69
        $o->setProperty2(new B);
70
71
        $deepCopy = new DeepCopy();
72
73
        $this->assertDeepCopyOf($o, $deepCopy->copy($o));
74
    }
75
76
    public function testPropertyArrayCopy()
77
    {
78
        $o = new A();
79
        $o->property1 = [new B()];
80
81
        $deepCopy = new DeepCopy();
82
83
        $this->assertDeepCopyOf($o, $deepCopy->copy($o));
84
    }
85
86
    public function testCycleCopy1()
87
    {
88
        $a = new A();
89
        $b = new B();
90
        $c = new B();
91
        $a->property1 = $b;
92
        $a->property2 = $c;
93
        $b->property = $c;
94
95
        $deepCopy = new DeepCopy();
96
        /** @var A $a2 */
97
        $a2 = $deepCopy->copy($a);
98
99
        $this->assertDeepCopyOf($a, $a2);
100
101
        $this->assertSame($a2->property1->property, $a2->property2);
102
    }
103
104
    public function testCycleCopy2()
105
    {
106
        $a = new B();
107
        $b = new B();
108
        $a->property = $b;
109
        $b->property = $a;
110
111
        $deepCopy = new DeepCopy();
112
        /** @var B $a2 */
113
        $a2 = $deepCopy->copy($a);
114
115
        $this->assertSame($a2, $a2->property->property);
116
    }
117
118
    /**
119
     * Dynamic properties should be cloned
120
     */
121
    public function testDynamicProperties()
122
    {
123
        $a = new \stdClass();
124
        $a->b = new \stdClass();
125
126
        $deepCopy = new DeepCopy();
127
        $a2 = $deepCopy->copy($a);
128
        $this->assertNotSame($a->b, $a2->b);
129
        $this->assertDeepCopyOf($a, $a2);
130
    }
131
132
    public function testCloneChild()
133
    {
134
        $h = new H();
135
136
        $deepCopy = new DeepCopy();
137
        $newH = $deepCopy->copy($h);
138
139
        $propRefl = (new \ReflectionObject($newH))->getProperty('prop');
140
        $propRefl->setAccessible(true);
141
142
        $this->assertNotSame($newH, $h);
143
        $this->assertEquals($newH, $h);
144
        $this->assertEquals('bar', $propRefl->getValue($newH));
145
    }
146
147
    public function testNonClonableItems()
148
    {
149
        $a = new \ReflectionClass('DeepCopyTest\A');
150
        $deepCopy = new DeepCopy();
151
        $a2 = $deepCopy->skipUncloneable()->copy($a);
152
        $this->assertSame($a, $a2);
153
    }
154
155
    /**
156
     * @expectedException \DeepCopy\Exception\CloneException
157
     * @expectedExceptionMessage Class "DeepCopyTest\C" is not cloneable.
158
     */
159
    public function testCloneException()
160
    {
161
        $o = new C;
162
        $deepCopy = new DeepCopy();
163
        $deepCopy->copy($o);
164
    }
165
166
    public function testCloneObjectsWithUserlandCloneMethod()
167
    {
168
        $f = new F();
169
        $f->prop = new \DateTime('2016-09-16');
170
171
        $deepCopy = new DeepCopy();
172
        $newF = $deepCopy->copy($f);
173
174
        $this->assertNotSame($newF->prop, $f->prop);
175
    }
176
177
    public function testCloneObjectsWithUserlandCloneMethodAndUseCloneableMethodEnabled()
178
    {
179
        $f = new F();
180
        $f->prop = new \DateTime('2016-09-16');
181
182
        $deepCopy = new DeepCopy(true);
183
        $newF = $deepCopy->copy($f);
184
185
        $this->assertSame($newF->prop, $f->prop);
186
    }
187
188
    /**
189
     * @test
190
     */
191
    public function filtersShouldBeApplied()
192
    {
193
        $o = new A();
194
        $o->property1 = 'foo';
195
196
        $filter = $this->getMockForAbstractClass('DeepCopy\Filter\Filter');
197
        $filter->expects($this->once())
198
            ->method('apply')
199
            ->will($this->returnCallback(function($object, $property) {
200
                        $object->$property = null;
201
                    }));
202
203
        $deepCopy = new DeepCopy();
204
        $deepCopy->addFilter($filter, new PropertyMatcher(get_class($o), 'property1'));
205
        /** @var A $new */
206
        $new = $deepCopy->copy($o);
207
208
        $this->assertNull($new->property1);
209
    }
210
211
    /**
212
     * If a filter applies to a property, the property shouldn't be copied
213
     * @test
214
     */
215
    public function filtersShouldBeAppliedAndBreakPropertyCopying()
216
    {
217
        $o = new A();
218
        $o->property1 = new B();
219
220
        /* @var Filter|\PHPUnit_Framework_MockObject_MockObject $filter */
221
        $filter = $this->getMockForAbstractClass('DeepCopy\Filter\Filter');
222
        $filter->expects($this->once())
0 ignored issues
show
Bug introduced by Matthieu Napoli
The method expects does only exist in PHPUnit_Framework_MockObject_MockObject, but not in DeepCopy\Filter\Filter.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
223
            ->method('apply')
224
            ->will($this->returnCallback(function($object, $property, $objectCopier) {
0 ignored issues
show
Unused Code introduced by Matthieu Napoli
The parameter $object is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by Matthieu Napoli
The parameter $property is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by Matthieu Napoli
The parameter $objectCopier is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
225
                    }));
226
227
        $deepCopy = new DeepCopy();
228
        $deepCopy->addFilter($filter, new PropertyMatcher(get_class($o), 'property1'));
0 ignored issues
show
Bug introduced by Matthieu Napoli
It seems like $filter defined by $this->getMockForAbstrac...pCopy\\Filter\\Filter') on line 221 can also be of type object<PHPUnit_Framework_MockObject_MockObject>; however, DeepCopy\DeepCopy::addFilter() does only seem to accept object<DeepCopy\Filter\Filter>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
229
        /** @var A $new */
230
        $new = $deepCopy->copy($o);
231
232
        $this->assertSame($o->property1, $new->property1);
233
    }
234
235
    /**
236
     * If a filter applies to an object, it should not be copied
237
     */
238
    public function testTypeFilterShouldBeAppliedOnObject()
239
    {
240
        $o = new A();
241
        $o->property1 = new B();
242
243
        /* @var TypeFilter|\PHPUnit_Framework_MockObject_MockObject $filter */
244
        $filter = $this->getMockForAbstractClass('DeepCopy\TypeFilter\TypeFilter');
245
        $filter->expects($this->once())
0 ignored issues
show
Bug introduced by Richard Trebichavský
The method expects does only exist in PHPUnit_Framework_MockObject_MockObject, but not in DeepCopy\TypeFilter\TypeFilter.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
246
            ->method('apply')
247
            ->will($this->returnValue(null));
248
249
        $deepCopy = new DeepCopy();
250
        $deepCopy->addTypeFilter($filter, new TypeMatcher('DeepCopyTest\B'));
0 ignored issues
show
Bug introduced by Richard Trebichavský
It seems like $filter defined by $this->getMockForAbstrac...ypeFilter\\TypeFilter') on line 244 can also be of type object<PHPUnit_Framework_MockObject_MockObject>; however, DeepCopy\DeepCopy::addTypeFilter() does only seem to accept object<DeepCopy\TypeFilter\TypeFilter>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
251
        /** @var A $new */
252
        $new = $deepCopy->copy($o);
253
254
        $this->assertNull($new->property1);
255
    }
256
257
    /**
258
     * If a filter applies to an array member, it should not be copied
259
     */
260
    public function testTypeFilterShouldBeAppliedOnArrayMember()
261
    {
262
        $arr = [new A, new A, new B, new B, new A];
263
264
        /* @var TypeFilter|\PHPUnit_Framework_MockObject_MockObject $filter */
265
        $filter = $this->getMockForAbstractClass('DeepCopy\TypeFilter\TypeFilter');
266
        $filter->expects($this->exactly(2))
0 ignored issues
show
Bug introduced by Richard Trebichavský
The method expects does only exist in PHPUnit_Framework_MockObject_MockObject, but not in DeepCopy\TypeFilter\TypeFilter.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
267
            ->method('apply')
268
            ->will($this->returnValue(null));
269
270
        $deepCopy = new DeepCopy();
271
        $deepCopy->addTypeFilter($filter, new TypeMatcher('DeepCopyTest\B'));
0 ignored issues
show
Bug introduced by Richard Trebichavský
It seems like $filter defined by $this->getMockForAbstrac...ypeFilter\\TypeFilter') on line 265 can also be of type object<PHPUnit_Framework_MockObject_MockObject>; however, DeepCopy\DeepCopy::addTypeFilter() does only seem to accept object<DeepCopy\TypeFilter\TypeFilter>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
272
        /** @var A $new */
273
        $new = $deepCopy->copy($arr);
274
275
        $this->assertNull($new[2]);
276
        $this->assertNull($new[3]);
277
    }
278
279
    public function testSplDoublyLinkedListDeepCopy()
280
    {
281
        $a = new A();
282
        $a->property1 = 'foo';
283
        $a->property2 = new \SplDoublyLinkedList();
284
285
        $b = new B();
286
        $b->property = 'baz';
287
        $a->property2->push($b);
288
289
        $stack = new \SplDoublyLinkedList();
290
        $stack->push($a);
291
        $stack->push($b);
292
293
        $deepCopy = new DeepCopy();
294
        $this->assertDeepCopyOf($stack, $deepCopy->copy($stack));
295
    }
296
}
297
298
class A
299
{
300
    public $property1;
301
    public $property2;
302
}
303
304
class B
305
{
306
    public $property;
307
}
308
309
class C
310
{
311
    private function __clone(){}
312
}
313
314
class D
315
{
316
    private $property1;
317
318
    public function getProperty1()
319
    {
320
        return $this->property1;
321
    }
322
323
    public function setProperty1($property1)
324
    {
325
        $this->property1 = $property1;
326
        return $this;
327
    }
328
}
329
330
class E extends D
331
{
332
    private $property2;
333
334
    public function getProperty2()
335
    {
336
        return $this->property2;
337
    }
338
339
    public function setProperty2($property2)
340
    {
341
        $this->property2 = $property2;
342
        return $this;
343
    }
344
}
345
346
class F
347
{
348
    public $prop;
349
350
    public function __clone()
351
    {
352
        $this->foo = 'bar';
0 ignored issues
show
Bug introduced by Théo FIDRY
The property foo does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
353
    }
354
}
355
356
class G
357
{
358
    private $prop = 'foo';
0 ignored issues
show
Unused Code introduced by Théo FIDRY
The property $prop is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
359
}
360
361
class H extends G
362
{
363
    private $prop = 'bar';
0 ignored issues
show
Comprehensibility introduced by Théo FIDRY
Consider using a different property name as you override a private property of the parent class.
Loading history...
Unused Code introduced by Théo FIDRY
The property $prop is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
364
}
365