Completed
Push — master ( 76e109...c917db )
by Valentin
02:55 queued 16s
created

ProxyPrinterTest::fetchUseStatements()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 15
rs 9.7666
c 0
b 0
f 0
cc 3
nc 3
nop 1
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()
23
    {
24
        $class = Fixtures\Entity::class;
25
        $as = "EntityProxy" . __LINE__;
26
27
        $output = $this->make($class, $as);
28
        $output = ltrim($output, "<?php");
29
30
        $schema = new Declaration($class, $as);
31
        $this->assertFalse(class_exists($schema->class->getNamespacesName()));
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, $schema->class->getNamespacesName());
44
45
        $this->assertInstanceOf($schema->class->getNamespacesName(), $proxy);
46
        $this->assertInstanceOf($class, $proxy);
47
        $this->assertInstanceOf(PromiseInterface::class, $proxy);
48
    }
49
50
    /**
51
     * @throws \ReflectionException
52
     */
53
    public function testSameNamespace()
54
    {
55
        $class = Fixtures\Entity::class;
56
        $as = "EntityProxy" . __LINE__;
57
58
        $output = $this->make($class, $as);
59
        $output = ltrim($output, "<?php");
60
61
        $schema = new Declaration($class, $as);
62
        $this->assertFalse(class_exists($schema->class->getNamespacesName()));
63
64
        eval($output);
65
66
        $origReflection = new \ReflectionClass($class);
67
        $proxyReflection = new \ReflectionClass($schema->class->getNamespacesName());
68
        $this->assertSame($origReflection->getNamespaceName(), $proxyReflection->getNamespaceName());
69
    }
70
71
    /**
72
     * @throws \ReflectionException
73
     */
74 View Code Duplication
    public function testDifferentNamespace()
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
        $output = $this->make($class, $as);
80
        $output = ltrim($output, "<?php");
81
82
        $schema = new Declaration($class, $as);
83
        $this->assertFalse(class_exists($schema->class->getNamespacesName()));
84
85
        eval($output);
86
87
        $proxyReflection = new \ReflectionClass($schema->class->getNamespacesName());
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()
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
        $output = $this->make($class, $as);
101
        $output = ltrim($output, "<?php");
102
103
        $schema = new Declaration($class, $as);
104
        $this->assertFalse(class_exists($schema->class->getNamespacesName()));
105
106
        eval($output);
107
108
        $this->assertSame($this->fetchUseStatements($output), $this->fetchExternalDependencies($schema->class->getNamespacesName(), [
109
            PromiseResolver::class,
110
            PromiseInterface::class
111
        ]));
112
    }
113
114
    /**
115
     * @throws \ReflectionException
116
     */
117 View Code Duplication
    public function testUseStmtsInDifferentNamespace()
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
        $output = $this->make($class, $as);
123
        $output = ltrim($output, "<?php");
124
125
        $schema = new Declaration($class, $as);
126
        $this->assertFalse(class_exists($schema->class->getNamespacesName()));
127
128
        eval($output);
129
130
        $this->assertSame($this->fetchUseStatements($output), $this->fetchExternalDependencies($schema->class->getNamespacesName(), [
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
    public function testTraits()
178
    {
179
        $this->assertStringNotContainsString(' use ', $this->make(Fixtures\EntityWithoutTrait::class, "EntityProxy" . __LINE__));
180
        $this->assertStringNotContainsString(' use ', $this->make(Fixtures\EntityWithTrait::class, "EntityProxy" . __LINE__));
181
    }
182
183
    public function testConstants()
184
    {
185
        $this->assertStringNotContainsString(' const ', $this->make(Fixtures\EntityWithoutConstants::class, "EntityProxy" . __LINE__));
186
        $this->assertStringNotContainsString(' const ', $this->make(Fixtures\EntityWithConstants::class, "EntityProxy" . __LINE__));
187
    }
188
189
    public function testProperties()
190
    {
191
        $class = Fixtures\Entity::class;
192
        $as = "EntityProxy" . __LINE__;
193
194
        $output = $this->make($class, $as);
195
        $output = ltrim($output, "<?php");
196
197
        $schema = new Declaration($class, $as);
198
        $this->assertFalse(class_exists($schema->class->getNamespacesName()));
199
200
        eval($output);
201
202
        $reflection = new \ReflectionClass($schema->class->getNamespacesName());
203
204
        /** @var \ReflectionProperty[] $properties */
205
        $properties = [];
206
        foreach ($reflection->getProperties() as $property) {
207
            if ($property->getDeclaringClass()->getName() !== $schema->class->getNamespacesName()) {
0 ignored issues
show
introduced by
Consider using $property->class. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
208
                continue;
209
            }
210
211
            $properties[] = $property;
212
        }
213
214
        $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...
215
        $property = $properties[0];
216
        $this->assertTrue($property->isPrivate());
217
        $this->assertFalse($property->isStatic());
218
        $this->assertStringContainsString('@var PromiseResolver|' . Utils::shortName($class), $property->getDocComment());
219
    }
220
221
    /**
222
     * @throws \ReflectionException
223
     */
224
    public function testHasConstructor()
225
    {
226
        $class = Fixtures\EntityWithoutConstructor::class;
227
        $as = "EntityProxy" . __LINE__;
228
229
        $output = $this->make($class, $as);
230
        $output = ltrim($output, "<?php");
231
232
        $schema = new Declaration($class, $as);
233
        $this->assertFalse(class_exists($schema->class->getNamespacesName()));
234
235
        eval($output);
236
237
        $reflection = new \ReflectionClass($schema->class->getNamespacesName());
238
        $constructor = $reflection->getConstructor();
239
        $this->assertNotNull($constructor);
240
241
        $class = Fixtures\EntityWithConstructor::class;
242
        $as = "EntityProxy" . __LINE__;
243
244
        $output = $this->make($class, $as);
245
        $output = ltrim($output, "<?php");
246
247
        $schema = new Declaration($class, $as);
248
        $this->assertFalse(class_exists($schema->class->getNamespacesName()));
249
250
        eval($output);
251
252
        $reflection = new \ReflectionClass($schema->class->getNamespacesName());
253
        $constructor = $reflection->getConstructor();
254
        $this->assertNotNull($constructor);
255
    }
256
257 View Code Duplication
    public function testNotContainParentConstructor()
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...
258
    {
259
        $class = Fixtures\EntityWithoutConstructor::class;
260
        $as = "EntityProxy" . __LINE__;
261
262
        $output = $this->make($class, $as);
263
        $output = ltrim($output, "<?php");
264
265
        $schema = new Declaration($class, $as);
266
        $this->assertFalse(class_exists($schema->class->getNamespacesName()));
267
268
        eval($output);
269
270
        $this->assertStringNotContainsString('parent::__construct();', $output);
271
    }
272
273 View Code Duplication
    public function testContainParentConstructor()
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...
274
    {
275
        $class = Fixtures\EntityWithConstructor::class;
276
        $as = "EntityProxy" . __LINE__;
277
278
        $output = $this->make($class, $as);
279
        $output = ltrim($output, "<?php");
280
281
        $schema = new Declaration($class, $as);
282
        $this->assertFalse(class_exists($schema->class->getNamespacesName()));
283
284
        eval($output);
285
286
        $this->assertStringContainsString('parent::__construct();', $output);
287
    }
288
289 View Code Duplication
    public function testPromiseMethods()
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...
290
    {
291
        $class = Fixtures\Entity::class;
292
        $as = "EntityProxy" . __LINE__;
293
294
        $output = $this->make($class, $as);
295
        $output = ltrim($output, "<?php");
296
297
        $schema = new Declaration($class, $as);
298
        $this->assertFalse(class_exists($schema->class->getNamespacesName()));
299
300
        eval($output);
301
302
        $i = new \ReflectionClass(PromiseInterface::class);
303
        foreach ($i->getMethods() as $method) {
304
            $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...
305
        }
306
    }
307
308
    public function testProxiedMethods()
309
    {
310
        $class = Fixtures\Entity::class;
311
        $as = "EntityProxy" . __LINE__;
312
313
        $output = $this->make($class, $as);
314
        $output = ltrim($output, "<?php");
315
316
        $schema = new Declaration($class, $as);
317
        $this->assertFalse(class_exists($schema->class->getNamespacesName()));
318
319
        eval($output);
320
321
        $d = $this->getDeclaration($class);
322
        foreach ($d->methods as $method) {
323
            if ($method->isPublic()) {
324
                $this->assertStringContainsString("public function {$method->name->name}()", $output);
325
            } elseif ($method->isProtected()) {
326
                $this->assertStringContainsString("protected function {$method->name->name}()", $output);
327
            } else {
328
                throw new \UnexpectedValueException("\"{$method->name->toString()}\" method not found");
329
            }
330
        }
331
    }
332
333
    private function getDeclaration(string $class): Structure
334
    {
335
        return $this->extractor()->extract($class);
336
    }
337
338
    private function extractor(): Extractor
339
    {
340
        $container = new Container();
341
342
        return $container->get(Extractor::class);
343
    }
344
345
    /**
346
     * @param string $className
347
     * @param string $proxyFullName
348
     *
349
     * @return object
350
     */
351
    private function makeProxyObject(string $className, string $proxyFullName)
352
    {
353
        $orm = \Mockery::mock(ORMInterface::class);
354
355
        $container = new Container();
356
        $container->bind(ORMInterface::class, $orm);
357
358
        return $container->make($proxyFullName, ['role' => $className, 'scope' => []]);
359
    }
360
361
    private function make(string $class, string $as): string
362
    {
363
        return $this->proxyCreator()->make($class, $as);
364
    }
365
366
    private function proxyCreator(): ProxyPrinter
367
    {
368
        $container = new Container();
369
        $container->bind(PrettyPrinterAbstract::class, Standard::class);
370
371
        return $container->get(ProxyPrinter::class);
372
    }
373
}