Passed
Push — master ( d3a94c...76e109 )
by Valentin
05:50
created

ProxyCreatorTest::testTraits()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 5
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
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;
8
use Cycle\ORM\Promise\Declaration\Schema;
9
use Cycle\ORM\Promise\PromiseInterface;
10
use Cycle\ORM\Promise\PromiseResolver;
11
use Cycle\ORM\Promise\ProxyCreator;
12
use Cycle\ORM\Promise\Tests\Fixtures;
13
use Cycle\ORM\Promise\Utils;
14
use PhpParser\PrettyPrinter\Standard;
15
use PhpParser\PrettyPrinterAbstract;
16
use PHPUnit\Framework\TestCase;
17
use Spiral\Core\Container;
18
19
class ProxyCreatorTest extends TestCase
20
{
21
    public function testDeclaration()
22
    {
23
        $class = Fixtures\Entity::class;
24
        $as = "EntityProxy" . __LINE__;
25
26
        $output = $this->make($class, $as);
27
        $output = ltrim($output, "<?php");
28
29
        $schema = new Schema($class, $as);
30
        $this->assertFalse(class_exists($schema->class->getNamespacesName()));
31
32
        eval($output);
33
34
        $this->assertStringNotContainsString('abstract', $output);
35
        $this->assertStringContainsString(sprintf(
36
            "class %s extends %s implements %s",
37
            $as,
38
            Utils::shortName($class),
39
            Utils::shortName(PromiseInterface::class)
40
        ), $output);
41
42
        $proxy = $this->makeProxyObject($class, $schema->class->getNamespacesName());
43
44
        $this->assertInstanceOf($schema->class->getNamespacesName(), $proxy);
45
        $this->assertInstanceOf($class, $proxy);
46
        $this->assertInstanceOf(PromiseInterface::class, $proxy);
47
    }
48
49
    /**
50
     * @throws \ReflectionException
51
     */
52
    public function testSameNamespace()
53
    {
54
        $class = Fixtures\Entity::class;
55
        $as = "EntityProxy" . __LINE__;
56
57
        $output = $this->make($class, $as);
58
        $output = ltrim($output, "<?php");
59
60
        $schema = new Schema($class, $as);
61
        $this->assertFalse(class_exists($schema->class->getNamespacesName()));
62
63
        eval($output);
64
65
        $origReflection = new \ReflectionClass($class);
66
        $proxyReflection = new \ReflectionClass($schema->class->getNamespacesName());
67
        $this->assertSame($origReflection->getNamespaceName(), $proxyReflection->getNamespaceName());
68
    }
69
70
    /**
71
     * @throws \ReflectionException
72
     */
73 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...
74
    {
75
        $class = Fixtures\Entity::class;
76
        $as = "\EntityProxy" . __LINE__;
77
78
        $output = $this->make($class, $as);
79
        $output = ltrim($output, "<?php");
80
81
        $schema = new Schema($class, $as);
82
        $this->assertFalse(class_exists($schema->class->getNamespacesName()));
83
84
        eval($output);
85
86
        $proxyReflection = new \ReflectionClass($schema->class->getNamespacesName());
87
        $this->assertSame('', (string)$proxyReflection->getNamespaceName());
88
        $this->assertStringNotContainsString('namespace ', $output);
89
    }
90
91
    /**
92
     * @throws \ReflectionException
93
     */
94 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...
95
    {
96
        $class = Fixtures\Entity::class;
97
        $as = "EntityProxy" . __LINE__;
98
99
        $output = $this->make($class, $as);
100
        $output = ltrim($output, "<?php");
101
102
        $schema = new Schema($class, $as);
103
        $this->assertFalse(class_exists($schema->class->getNamespacesName()));
104
105
        eval($output);
106
107
        $this->assertSame($this->fetchUseStatements($output), $this->fetchExternalDependencies($schema->class->getNamespacesName(), [
108
            PromiseResolver::class,
109
            PromiseInterface::class
110
        ]));
111
    }
112
113
    /**
114
     * @throws \ReflectionException
115
     */
116 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...
117
    {
118
        $class = Fixtures\Entity::class;
119
        $as = "\EntityProxy" . __LINE__;
120
121
        $output = $this->make($class, $as);
122
        $output = ltrim($output, "<?php");
123
124
        $schema = new Schema($class, $as);
125
        $this->assertFalse(class_exists($schema->class->getNamespacesName()));
126
127
        eval($output);
128
129
        $this->assertSame($this->fetchUseStatements($output), $this->fetchExternalDependencies($schema->class->getNamespacesName(), [
130
            PromiseResolver::class,
131
            PromiseInterface::class,
132
            $class
133
        ]));
134
    }
135
136
    private function fetchUseStatements(string $code): array
137
    {
138
        $uses = [];
139
        foreach (explode("\n", $code) as $line) {
140
            if (mb_stripos($line, 'use') !== 0) {
141
                continue;
142
            }
143
144
            $uses[] = trim(mb_substr($line, 4), " ;\r\n");
145
        }
146
147
        sort($uses);
148
149
        return $uses;
150
    }
151
152
    /**
153
     * @param string $class
154
     * @param array  $types
155
     *
156
     * @return array
157
     * @throws \ReflectionException
158
     */
159
    private function fetchExternalDependencies(string $class, array $types = []): array
160
    {
161
        $reflection = new \ReflectionClass($class);
162
163
        foreach ($reflection->getConstructor()->getParameters() as $parameter) {
164
            if (!$parameter->hasType() || $parameter->getType()->isBuiltin()) {
165
                continue;
166
            }
167
168
            $types[] = $parameter->getType()->getName();
169
        }
170
171
        sort($types);
172
173
        return $types;
174
    }
175
176
    public function testTraits()
177
    {
178
        $this->assertStringNotContainsString(' use ', $this->make(Fixtures\EntityWithoutTrait::class, "EntityProxy" . __LINE__));
179
        $this->assertStringNotContainsString(' use ', $this->make(Fixtures\EntityWithTrait::class, "EntityProxy" . __LINE__));
180
    }
181
182
    public function testConstants()
183
    {
184
        $this->assertStringNotContainsString(' const ', $this->make(Fixtures\EntityWithoutConstants::class, "EntityProxy" . __LINE__));
185
        $this->assertStringNotContainsString(' const ', $this->make(Fixtures\EntityWithConstants::class, "EntityProxy" . __LINE__));
186
    }
187
188
    public function testProperties()
189
    {
190
        $class = Fixtures\Entity::class;
191
        $as = "EntityProxy" . __LINE__;
192
193
        $output = $this->make($class, $as);
194
        $output = ltrim($output, "<?php");
195
196
        $schema = new Schema($class, $as);
197
        $this->assertFalse(class_exists($schema->class->getNamespacesName()));
198
199
        eval($output);
200
201
        $reflection = new \ReflectionClass($schema->class->getNamespacesName());
202
203
        /** @var \ReflectionProperty[] $properties */
204
        $properties = [];
205
        foreach ($reflection->getProperties() as $property) {
206
            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...
207
                continue;
208
            }
209
210
            $properties[] = $property;
211
        }
212
213
        $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...
214
        $property = $properties[0];
215
        $this->assertTrue($property->isPrivate());
216
        $this->assertFalse($property->isStatic());
217
        $this->assertStringContainsString('@var PromiseResolver|' . Utils::shortName($class), $property->getDocComment());
218
    }
219
220
    /**
221
     * @throws \ReflectionException
222
     */
223
    public function testHasConstructor()
224
    {
225
        $class = Fixtures\EntityWithoutConstructor::class;
226
        $as = "EntityProxy" . __LINE__;
227
228
        $output = $this->make($class, $as);
229
        $output = ltrim($output, "<?php");
230
231
        $schema = new Schema($class, $as);
232
        $this->assertFalse(class_exists($schema->class->getNamespacesName()));
233
234
        eval($output);
235
236
        $reflection = new \ReflectionClass($schema->class->getNamespacesName());
237
        $constructor = $reflection->getConstructor();
238
        $this->assertNotNull($constructor);
239
240
        $class = Fixtures\EntityWithConstructor::class;
241
        $as = "EntityProxy" . __LINE__;
242
243
        $output = $this->make($class, $as);
244
        $output = ltrim($output, "<?php");
245
246
        $schema = new Schema($class, $as);
247
        $this->assertFalse(class_exists($schema->class->getNamespacesName()));
248
249
        eval($output);
250
251
        $reflection = new \ReflectionClass($schema->class->getNamespacesName());
252
        $constructor = $reflection->getConstructor();
253
        $this->assertNotNull($constructor);
254
    }
255
256 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...
257
    {
258
        $class = Fixtures\EntityWithoutConstructor::class;
259
        $as = "EntityProxy" . __LINE__;
260
261
        $output = $this->make($class, $as);
262
        $output = ltrim($output, "<?php");
263
264
        $schema = new Schema($class, $as);
265
        $this->assertFalse(class_exists($schema->class->getNamespacesName()));
266
267
        eval($output);
268
269
        $this->assertStringNotContainsString('parent::__construct();', $output);
270
    }
271
272 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...
273
    {
274
        $class = Fixtures\EntityWithConstructor::class;
275
        $as = "EntityProxy" . __LINE__;
276
277
        $output = $this->make($class, $as);
278
        $output = ltrim($output, "<?php");
279
280
        $schema = new Schema($class, $as);
281
        $this->assertFalse(class_exists($schema->class->getNamespacesName()));
282
283
        eval($output);
284
285
        $this->assertStringContainsString('parent::__construct();', $output);
286
    }
287
288 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...
289
    {
290
        $class = Fixtures\Entity::class;
291
        $as = "EntityProxy" . __LINE__;
292
293
        $output = $this->make($class, $as);
294
        $output = ltrim($output, "<?php");
295
296
        $schema = new Schema($class, $as);
297
        $this->assertFalse(class_exists($schema->class->getNamespacesName()));
298
299
        eval($output);
300
301
        $i = new \ReflectionClass(PromiseInterface::class);
302
        foreach ($i->getMethods() as $method) {
303
            $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...
304
        }
305
    }
306
307
    public function testProxiedMethods()
308
    {
309
        $class = Fixtures\Entity::class;
310
        $as = "EntityProxy" . __LINE__;
311
312
        $output = $this->make($class, $as);
313
        $output = ltrim($output, "<?php");
314
315
        $schema = new Schema($class, $as);
316
        $this->assertFalse(class_exists($schema->class->getNamespacesName()));
317
318
        eval($output);
319
320
        $d = $this->getDeclaration($class);
321
        foreach ($d->methods as $method) {
322
            if ($method->isPublic()) {
323
                $this->assertStringContainsString("public function {$method->name->name}()", $output);
324
            } elseif ($method->isProtected()) {
325
                $this->assertStringContainsString("protected function {$method->name->name}()", $output);
326
            } else {
327
                throw new \UnexpectedValueException("\"{$method->name->toString()}\" method not found");
328
            }
329
        }
330
    }
331
332
    private function getDeclaration(string $class): Declaration\Declaration
333
    {
334
        return $this->extractor()->extract($class);
335
    }
336
337
    private function extractor(): Declaration\Extractor
338
    {
339
        $container = new Container();
340
341
        return $container->get(Declaration\Extractor::class);
342
    }
343
344
    /**
345
     * @param string $className
346
     * @param string $proxyFullName
347
     *
348
     * @return object
349
     */
350
    private function makeProxyObject(string $className, string $proxyFullName)
351
    {
352
        $orm = \Mockery::mock(ORMInterface::class);
353
354
        $container = new Container();
355
        $container->bind(ORMInterface::class, $orm);
356
357
        return $container->make($proxyFullName, ['target' => $className, 'scope' => []]);
358
    }
359
360
    private function make(string $class, string $as): string
361
    {
362
        return $this->proxyCreator()->make($class, $as);
363
    }
364
365
    private function proxyCreator(): ProxyCreator
366
    {
367
        $container = new Container();
368
        $container->bind(PrettyPrinterAbstract::class, Standard::class);
369
370
        return $container->get(ProxyCreator::class);
371
    }
372
}