Completed
Push — master ( f6b031...94e5ca )
by Matthieu
02:33
created

testCloneObjectsWithUserlandCloneMethod()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 10
rs 9.4285
cc 1
eloc 6
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\TypeFilter\TypeFilter;
9
use DeepCopy\TypeMatcher\TypeMatcher;
10
11
/**
12
 * DeepCopyTest
13
 */
14
class DeepCopyTest extends AbstractTestClass
15
{
16
    public function testSimpleObjectCopy()
17
    {
18
        $o = new A();
19
20
        $deepCopy = new DeepCopy();
21
22
        $this->assertDeepCopyOf($o, $deepCopy->copy($o));
23
    }
24
25
    public function testPropertyScalarCopy()
26
    {
27
        $o = new A();
28
        $o->property1 = 'foo';
29
30
        $deepCopy = new DeepCopy();
31
32
        $this->assertDeepCopyOf($o, $deepCopy->copy($o));
33
    }
34
35
    public function testPropertyObjectCopy()
36
    {
37
        $o = new A();
38
        $o->property1 = new B();
39
40
        $deepCopy = new DeepCopy();
41
42
        $this->assertDeepCopyOf($o, $deepCopy->copy($o));
43
    }
44
45
    public function testPropertyObjectCopyWithDateTimes()
46
    {
47
        $o = new A();
48
        $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...
49
        if (class_exists('DateTimeImmutable')) {
50
            $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...
51
        }
52
53
        $deepCopy = new DeepCopy();
54
        $c = $deepCopy->copy($o);
55
56
        $this->assertDeepCopyOf($o, $c);
57
58
        $c->date1->setDate(2015, 01, 04);
59
        $this->assertNotEquals($c->date1, $o->date1);
60
    }
61
62
    public function testPrivatePropertyOfParentObjectCopy()
63
    {
64
        $o = new E();
65
        $o->setProperty1(new B);
66
        $o->setProperty2(new B);
67
68
        $deepCopy = new DeepCopy();
69
70
        $this->assertDeepCopyOf($o, $deepCopy->copy($o));
71
    }
72
73
    public function testPropertyArrayCopy()
74
    {
75
        $o = new A();
76
        $o->property1 = [new B()];
77
78
        $deepCopy = new DeepCopy();
79
80
        $this->assertDeepCopyOf($o, $deepCopy->copy($o));
81
    }
82
83
    public function testCycleCopy1()
84
    {
85
        $a = new A();
86
        $b = new B();
87
        $c = new B();
88
        $a->property1 = $b;
89
        $a->property2 = $c;
90
        $b->property = $c;
91
92
        $deepCopy = new DeepCopy();
93
        /** @var A $a2 */
94
        $a2 = $deepCopy->copy($a);
95
96
        $this->assertDeepCopyOf($a, $a2);
97
98
        $this->assertSame($a2->property1->property, $a2->property2);
99
    }
100
101
    public function testCycleCopy2()
102
    {
103
        $a = new B();
104
        $b = new B();
105
        $a->property = $b;
106
        $b->property = $a;
107
108
        $deepCopy = new DeepCopy();
109
        /** @var B $a2 */
110
        $a2 = $deepCopy->copy($a);
111
112
        $this->assertSame($a2, $a2->property->property);
113
    }
114
115
    /**
116
     * Dynamic properties should be cloned
117
     */
118
    public function testDynamicProperties()
119
    {
120
        $a = new \stdClass();
121
        $a->b = new \stdClass();
122
123
        $deepCopy = new DeepCopy();
124
        $a2 = $deepCopy->copy($a);
125
        $this->assertNotSame($a->b, $a2->b);
126
        $this->assertDeepCopyOf($a, $a2);
127
    }
128
129
    public function testNonClonableItems()
130
    {
131
        $a = new \ReflectionClass('DeepCopyTest\A');
132
        $deepCopy = new DeepCopy();
133
        $a2 = $deepCopy->skipUncloneable()->copy($a);
134
        $this->assertSame($a, $a2);
135
    }
136
137
    /**
138
     * @expectedException \DeepCopy\Exception\CloneException
139
     * @expectedExceptionMessage Class "DeepCopyTest\C" is not cloneable.
140
     */
141
    public function testCloneException()
142
    {
143
        $o = new C;
144
        $deepCopy = new DeepCopy();
145
        $deepCopy->copy($o);
146
    }
147
    
148
    public function testCloneObjectsWithUserlandCloneMethod()
149
    {
150
        $f = new F();
151
        $f->prop = new \DateTime('2016-09-16');
152
153
        $deepCopy = new DeepCopy();
154
        $newF = $deepCopy->copy($f);
155
156
        $this->assertNotSame($newF->prop, $f->prop);
157
    }
158
159
    public function testCloneObjectsWithUserlandCloneMethodAndUseCloneableMethodEnabled()
160
    {
161
        $f = new F();
162
        $f->prop = new \DateTime('2016-09-16');
163
164
        $deepCopy = new DeepCopy(true);
165
        $newF = $deepCopy->copy($f);
166
167
        $this->assertSame($newF->prop, $f->prop);
168
    }
169
170
    /**
171
     * @test
172
     */
173
    public function filtersShouldBeApplied()
174
    {
175
        $o = new A();
176
        $o->property1 = 'foo';
177
178
        $filter = $this->getMockForAbstractClass('DeepCopy\Filter\Filter');
179
        $filter->expects($this->once())
180
            ->method('apply')
181
            ->will($this->returnCallback(function($object, $property) {
182
                        $object->$property = null;
183
                    }));
184
185
        $deepCopy = new DeepCopy();
186
        $deepCopy->addFilter($filter, new PropertyMatcher(get_class($o), 'property1'));
187
        /** @var A $new */
188
        $new = $deepCopy->copy($o);
189
190
        $this->assertNull($new->property1);
191
    }
192
193
    /**
194
     * If a filter applies to a property, the property shouldn't be copied
195
     * @test
196
     */
197
    public function filtersShouldBeAppliedAndBreakPropertyCopying()
198
    {
199
        $o = new A();
200
        $o->property1 = new B();
201
202
        /* @var Filter|\PHPUnit_Framework_MockObject_MockObject $filter */
203
        $filter = $this->getMockForAbstractClass('DeepCopy\Filter\Filter');
204
        $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...
205
            ->method('apply')
206
            ->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...
207
                    }));
208
209
        $deepCopy = new DeepCopy();
210
        $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 203 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...
211
        /** @var A $new */
212
        $new = $deepCopy->copy($o);
213
214
        $this->assertSame($o->property1, $new->property1);
215
    }
216
217
    /**
218
     * If a filter applies to an object, it should not be copied
219
     */
220
    public function testTypeFilterShouldBeAppliedOnObject()
221
    {
222
        $o = new A();
223
        $o->property1 = new B();
224
225
        /* @var TypeFilter|\PHPUnit_Framework_MockObject_MockObject $filter */
226
        $filter = $this->getMockForAbstractClass('DeepCopy\TypeFilter\TypeFilter');
227
        $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...
228
            ->method('apply')
229
            ->will($this->returnValue(null));
230
231
        $deepCopy = new DeepCopy();
232
        $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 226 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...
233
        /** @var A $new */
234
        $new = $deepCopy->copy($o);
235
236
        $this->assertNull($new->property1);
237
    }
238
239
    /**
240
     * If a filter applies to an array member, it should not be copied
241
     */
242
    public function testTypeFilterShouldBeAppliedOnArrayMember()
243
    {
244
        $arr = [new A, new A, new B, new B, new A];
245
246
        /* @var TypeFilter|\PHPUnit_Framework_MockObject_MockObject $filter */
247
        $filter = $this->getMockForAbstractClass('DeepCopy\TypeFilter\TypeFilter');
248
        $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...
249
            ->method('apply')
250
            ->will($this->returnValue(null));
251
252
        $deepCopy = new DeepCopy();
253
        $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 247 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...
254
        /** @var A $new */
255
        $new = $deepCopy->copy($arr);
256
257
        $this->assertNull($new[2]);
258
        $this->assertNull($new[3]);
259
    }
260
}
261
262
class A
263
{
264
    public $property1;
265
    public $property2;
266
}
267
268
class B
269
{
270
    public $property;
271
}
272
273
class C
274
{
275
    private function __clone(){}
276
}
277
278
class D
279
{
280
    private $property1;
281
282
    public function getProperty1()
283
    {
284
        return $this->property1;
285
    }
286
287
    public function setProperty1($property1)
288
    {
289
        $this->property1 = $property1;
290
        return $this;
291
    }
292
}
293
294
class E extends D
295
{
296
    private $property2;
297
298
    public function getProperty2()
299
    {
300
        return $this->property2;
301
    }
302
303
    public function setProperty2($property2)
304
    {
305
        $this->property2 = $property2;
306
        return $this;
307
    }
308
}
309
310
class F
311
{
312
    public $prop;
313
314
    public function __clone()
315
    {
316
        $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...
317
    }
318
}
319