Completed
Push — master ( d154ad...604248 )
by Valentin
05:10
created

ProxyPrinterTest   A

Complexity

Total Complexity 36

Size/Duplication

Total Lines 390
Duplicated Lines 29.74 %

Coupling/Cohesion

Components 1
Dependencies 11

Importance

Changes 0
Metric Value
wmc 36
lcom 1
cbo 11
dl 116
loc 390
rs 9.52
c 0
b 0
f 0

20 Methods

Rating   Name   Duplication   Size   Complexity  
A testDeclaration() 0 27 1
A testSameNamespace() 0 17 1
A testDifferentNamespace() 17 17 1
A testUseStmtsInSameNamespace() 18 18 1
A testUseStmtsInDifferentNamespace() 19 19 1
A fetchUseStatements() 0 15 3
A fetchExternalDependencies() 0 16 4
A testTraits() 7 7 1
A testConstants() 7 7 1
A testProperties() 0 31 3
A testHasConstructor() 0 32 1
A testNotContainParentConstructor() 15 15 1
A testContainParentConstructor() 15 15 1
A testPromiseMethods() 18 18 2
B testInheritedMethods() 0 52 9
A getDeclaration() 0 4 1
A extractor() 0 6 1
A makeProxyObject() 0 9 1
A make() 0 4 1
A proxyCreator() 0 7 1

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

1
<?php
2
declare(strict_types=1);
3
4
namespace Cycle\ORM\Promise\Tests;
5
6
use Cycle\ORM\ORMInterface;
7
use Cycle\ORM\Promise\Declaration\Declaration;
8
use Cycle\ORM\Promise\Declaration\Extractor;
9
use Cycle\ORM\Promise\Declaration\Structure;
10
use Cycle\ORM\Promise\PromiseInterface;
11
use Cycle\ORM\Promise\PromiseResolver;
12
use Cycle\ORM\Promise\ProxyPrinter;
13
use Cycle\ORM\Promise\Tests\Fixtures;
14
use Cycle\ORM\Promise\Utils;
15
use PhpParser\PrettyPrinter\Standard;
16
use PhpParser\PrettyPrinterAbstract;
17
use PHPUnit\Framework\TestCase;
18
use Spiral\Core\Container;
19
20
class ProxyPrinterTest extends TestCase
21
{
22
    public function testDeclaration(): void
23
    {
24
        $class = Fixtures\Entity::class;
25
        $as = 'EntityProxy' . __LINE__;
26
27
        $declaration = new Declaration(new \ReflectionClass($class), $as);
28
        $output = $this->make($declaration);
29
        $output = ltrim($output, '<?php');
30
31
        $this->assertFalse(class_exists($declaration->class->getFullName()));
32
33
        eval($output);
34
35
        $this->assertStringNotContainsString('abstract', $output);
36
        $this->assertStringContainsString(sprintf(
37
            'class %s extends %s implements %s',
38
            $as,
39
            Utils::shortName($class),
40
            Utils::shortName(PromiseInterface::class)
41
        ), $output);
42
43
        $proxy = $this->makeProxyObject($class, $declaration->class->getFullName());
44
45
        $this->assertInstanceOf($declaration->class->getFullName(), $proxy);
46
        $this->assertInstanceOf($class, $proxy);
47
        $this->assertInstanceOf(PromiseInterface::class, $proxy);
48
    }
49
50
    /**
51
     * @throws \ReflectionException
52
     */
53
    public function testSameNamespace(): void
54
    {
55
        $class = Fixtures\Entity::class;
56
        $as = 'EntityProxy' . __LINE__;
57
58
        $declaration = new Declaration(new \ReflectionClass($class), $as);
59
        $output = $this->make($declaration);
60
        $output = ltrim($output, '<?php');
61
62
        $this->assertFalse(class_exists($declaration->class->getFullName()));
63
64
        eval($output);
65
66
        $origReflection = new \ReflectionClass($class);
67
        $proxyReflection = new \ReflectionClass($declaration->class->getFullName());
68
        $this->assertSame($origReflection->getNamespaceName(), $proxyReflection->getNamespaceName());
69
    }
70
71
    /**
72
     * @throws \ReflectionException
73
     */
74 View Code Duplication
    public function testDifferentNamespace(): void
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
75
    {
76
        $class = Fixtures\Entity::class;
77
        $as = "\EntityProxy" . __LINE__;
78
79
        $declaration = new Declaration(new \ReflectionClass($class), $as);
80
        $output = $this->make($declaration);
81
        $output = ltrim($output, '<?php');
82
83
        $this->assertFalse(class_exists($declaration->class->getFullName()));
84
85
        eval($output);
86
87
        $proxyReflection = new \ReflectionClass($declaration->class->getFullName());
88
        $this->assertSame('', (string)$proxyReflection->getNamespaceName());
89
        $this->assertStringNotContainsString('namespace ', $output);
90
    }
91
92
    /**
93
     * @throws \ReflectionException
94
     */
95 View Code Duplication
    public function testUseStmtsInSameNamespace(): void
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
96
    {
97
        $class = Fixtures\Entity::class;
98
        $as = 'EntityProxy' . __LINE__;
99
100
        $declaration = new Declaration(new \ReflectionClass($class), $as);
101
        $output = $this->make($declaration);
102
        $output = ltrim($output, '<?php');
103
104
        $this->assertFalse(class_exists($declaration->class->getFullName()));
105
106
        eval($output);
107
108
        $this->assertSame($this->fetchUseStatements($output), $this->fetchExternalDependencies($declaration->class->getFullName(), [
109
            PromiseResolver::class,
110
            PromiseInterface::class
111
        ]));
112
    }
113
114
    /**
115
     * @throws \ReflectionException
116
     */
117 View Code Duplication
    public function testUseStmtsInDifferentNamespace(): void
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
118
    {
119
        $class = Fixtures\Entity::class;
120
        $as = "\EntityProxy" . __LINE__;
121
122
        $declaration = new Declaration(new \ReflectionClass($class), $as);
123
        $output = $this->make($declaration);
124
        $output = ltrim($output, '<?php');
125
126
        $this->assertFalse(class_exists($declaration->class->getFullName()));
127
128
        eval($output);
129
130
        $this->assertSame($this->fetchUseStatements($output), $this->fetchExternalDependencies($declaration->class->getFullName(), [
131
            PromiseResolver::class,
132
            PromiseInterface::class,
133
            $class
134
        ]));
135
    }
136
137
    private function fetchUseStatements(string $code): array
138
    {
139
        $uses = [];
140
        foreach (explode("\n", $code) as $line) {
141
            if (mb_stripos($line, 'use') !== 0) {
142
                continue;
143
            }
144
145
            $uses[] = trim(mb_substr($line, 4), " ;\r\n");
146
        }
147
148
        sort($uses);
149
150
        return $uses;
151
    }
152
153
    /**
154
     * @param string $class
155
     * @param array  $types
156
     *
157
     * @return array
158
     * @throws \ReflectionException
159
     */
160
    private function fetchExternalDependencies(string $class, array $types = []): array
161
    {
162
        $reflection = new \ReflectionClass($class);
163
164
        foreach ($reflection->getConstructor()->getParameters() as $parameter) {
165
            if (!$parameter->hasType() || $parameter->getType()->isBuiltin()) {
166
                continue;
167
            }
168
169
            $types[] = $parameter->getType()->getName();
170
        }
171
172
        sort($types);
173
174
        return $types;
175
    }
176
177 View Code Duplication
    public function testTraits(): void
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
178
    {
179
        $this->assertStringNotContainsString(' use ',
180
            $this->make(new Declaration(new \ReflectionClass(Fixtures\EntityWithoutTrait::class), 'EntityProxy' . __LINE__)));
181
        $this->assertStringNotContainsString(' use ',
182
            $this->make(new Declaration(new \ReflectionClass(Fixtures\EntityWithTrait::class), 'EntityProxy' . __LINE__)));
183
    }
184
185 View Code Duplication
    public function testConstants(): void
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
186
    {
187
        $this->assertStringNotContainsString(' const ',
188
            $this->make(new Declaration(new \ReflectionClass(Fixtures\EntityWithoutConstants::class), 'EntityProxy' . __LINE__)));
189
        $this->assertStringNotContainsString(' const ',
190
            $this->make(new Declaration(new \ReflectionClass(Fixtures\EntityWithConstants::class), 'EntityProxy' . __LINE__)));
191
    }
192
193
    public function testProperties(): void
194
    {
195
        $class = Fixtures\Entity::class;
196
        $as = 'EntityProxy' . __LINE__;
197
198
        $declaration = new Declaration(new \ReflectionClass($class), $as);
199
        $output = $this->make($declaration);
200
        $output = ltrim($output, '<?php');
201
202
        $this->assertFalse(class_exists($declaration->class->getFullName()));
203
204
        eval($output);
205
206
        $reflection = new \ReflectionClass($declaration->class->getFullName());
207
208
        /** @var \ReflectionProperty[] $properties */
209
        $properties = [];
210
        foreach ($reflection->getProperties() as $property) {
211
            if ($property->getDeclaringClass()->getName() !== $declaration->class->getFullName()) {
0 ignored issues
show
introduced by
Consider using $property->class. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
212
                continue;
213
            }
214
215
            $properties[] = $property;
216
        }
217
218
        $this->assertCount(1, $properties);
0 ignored issues
show
Documentation introduced by
$properties is of type array<integer,object<ReflectionProperty>>, but the function expects a object<Countable>|object...nit\Framework\iterable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
219
        $property = $properties[0];
220
        $this->assertTrue($property->isPrivate());
221
        $this->assertFalse($property->isStatic());
222
        $this->assertStringContainsString('@var PromiseResolver|' . Utils::shortName($class), $property->getDocComment());
223
    }
224
225
    /**
226
     * @throws \ReflectionException
227
     */
228
    public function testHasConstructor(): void
229
    {
230
        $class = Fixtures\EntityWithoutConstructor::class;
231
        $as = 'EntityProxy' . __LINE__;
232
233
        $declaration = new Declaration(new \ReflectionClass($class), $as);
234
        $output = $this->make($declaration);
235
        $output = ltrim($output, '<?php');
236
237
        $this->assertFalse(class_exists($declaration->class->getFullName()));
238
239
        eval($output);
240
241
        $reflection = new \ReflectionClass($declaration->class->getFullName());
242
        $constructor = $reflection->getConstructor();
243
        $this->assertNotNull($constructor);
244
245
        $class = Fixtures\EntityWithConstructor::class;
246
        $as = 'EntityProxy' . __LINE__;
247
248
        $declaration = new Declaration(new \ReflectionClass($class), $as);
249
        $output = $this->make($declaration);
250
        $output = ltrim($output, '<?php');
251
252
        $this->assertFalse(class_exists($declaration->class->getFullName()));
253
254
        eval($output);
255
256
        $reflection = new \ReflectionClass($declaration->class->getFullName());
257
        $constructor = $reflection->getConstructor();
258
        $this->assertNotNull($constructor);
259
    }
260
261 View Code Duplication
    public function testNotContainParentConstructor(): void
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
262
    {
263
        $class = Fixtures\EntityWithoutConstructor::class;
264
        $as = 'EntityProxy' . __LINE__;
265
266
        $declaration = new Declaration(new \ReflectionClass($class), $as);
267
        $output = $this->make($declaration);
268
        $output = ltrim($output, '<?php');
269
270
        $this->assertFalse(class_exists($declaration->class->getFullName()));
271
272
        eval($output);
273
274
        $this->assertStringNotContainsString('parent::__construct();', $output);
275
    }
276
277 View Code Duplication
    public function testContainParentConstructor(): void
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
278
    {
279
        $class = Fixtures\EntityWithConstructor::class;
280
        $as = 'EntityProxy' . __LINE__;
281
282
        $declaration = new Declaration(new \ReflectionClass($class), $as);
283
        $output = $this->make($declaration);
284
        $output = ltrim($output, '<?php');
285
286
        $this->assertFalse(class_exists($declaration->class->getFullName()));
287
288
        eval($output);
289
290
        $this->assertStringContainsString('parent::__construct();', $output);
291
    }
292
293 View Code Duplication
    public function testPromiseMethods(): void
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
294
    {
295
        $class = Fixtures\Entity::class;
296
        $as = 'EntityProxy' . __LINE__;
297
298
        $declaration = new Declaration(new \ReflectionClass($class), $as);
299
        $output = $this->make($declaration);
300
        $output = ltrim($output, '<?php');
301
302
        $this->assertFalse(class_exists($declaration->class->getFullName()));
303
304
        eval($output);
305
306
        $i = new \ReflectionClass(PromiseInterface::class);
307
        foreach ($i->getMethods() as $method) {
308
            $this->assertStringContainsString("public function {$method->getName()}()", $output);
0 ignored issues
show
Bug introduced by
Consider using $method->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
309
        }
310
    }
311
312
//    public function testInheritedProperties(): void
313
//    {
314
//    }
315
316
    public function testInheritedMethods(): void
317
    {
318
        $class = Fixtures\ChildEntity::class;
319
        $as = 'EntityProxy' . __LINE__;
320
321
        $reflection = new \ReflectionClass($class);
322
323
        $declaration = new Declaration($reflection, $as);
324
        $output = $this->make($declaration);
325
        $output = ltrim($output, '<?php');
326
327
        $this->assertFalse(class_exists($declaration->class->getFullName()));
328
329
        eval($output);
330
331
        $sourceMethods = [];
332
333
        //There're only public and protected methods inside
334
        foreach ($reflection->getMethods() as $method) {
335
            $sourceMethods[$method->getName()] = $method->isPublic() ? 'public' : 'protected';
0 ignored issues
show
Bug introduced by
Consider using $method->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
336
        }
337
338
        /** @var \PhpParser\Node\Stmt\ClassMethod[] $methods */
339
        $methods = [];
340
        foreach ($this->getDeclaration($class)->methods as $method) {
341
            $methods[$method->name->name] = $method;
342
        }
343
344
        foreach ($sourceMethods as $name => $accessor) {
345
            $this->assertArrayHasKey($name, $methods, "Proxy class does not contain expected `{$name}` method");
346
347
            if ($accessor === 'public') {
348
                $this->assertTrue($methods[$name]->isPublic(), "Proxied method `{$name}` expected to be public");
349
                $this->assertStringContainsString("public function {$name}()", $output);
350
            } else {
351
                $this->assertTrue($methods[$name]->isProtected(), "Proxied method `{$name}` expected to be protected");
352
                $this->assertStringContainsString("protected function {$name}()", $output);
353
            }
354
        }
355
356
        foreach ($methods as $name => $method) {
357
            $this->assertArrayHasKey($name, $sourceMethods, "Origin class does not contain expected `{$name}` method");
358
359
            if ($method->isPublic()) {
360
                $this->assertEquals('public', $sourceMethods[$name], "Proxied method `{$name}` expected to be public");
361
            } elseif ($method->isProtected()) {
362
                $this->assertEquals('protected', $sourceMethods[$name], "Proxied method `{$name}` expected to be public");
363
            } else {
364
                throw new \UnexpectedValueException("\"{$method->name->toString()}\" method not found");
365
            }
366
        }
367
    }
368
369
    private function getDeclaration(string $class): Structure
370
    {
371
        return $this->extractor()->extract($class);
372
    }
373
374
    private function extractor(): Extractor
375
    {
376
        $container = new Container();
377
378
        return $container->get(Extractor::class);
379
    }
380
381
    /**
382
     * @param string $className
383
     * @param string $proxyFullName
384
     *
385
     * @return object
386
     */
387
    private function makeProxyObject(string $className, string $proxyFullName)
388
    {
389
        $orm = \Mockery::mock(ORMInterface::class);
390
391
        $container = new Container();
392
        $container->bind(ORMInterface::class, $orm);
393
394
        return $container->make($proxyFullName, ['role' => $className, 'scope' => []]);
395
    }
396
397
    private function make(Declaration $declaration): string
398
    {
399
        return $this->proxyCreator()->make($declaration);
400
    }
401
402
    private function proxyCreator(): ProxyPrinter
403
    {
404
        $container = new Container();
405
        $container->bind(PrettyPrinterAbstract::class, Standard::class);
406
407
        return $container->get(ProxyPrinter::class);
408
    }
409
}