These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | declare(strict_types=1); |
||
3 | |||
4 | namespace Cycle\ORM\Promise\Tests; |
||
5 | |||
6 | use Cycle\Annotated\Entities; |
||
7 | use Cycle\ORM\ORMInterface; |
||
8 | use Cycle\ORM\Promise\Declaration\Declarations; |
||
9 | use Cycle\ORM\Promise\Declaration\Extractor; |
||
10 | use Cycle\ORM\Promise\Declaration\Structure; |
||
11 | use Cycle\ORM\Promise\PromiseInterface; |
||
12 | use Cycle\ORM\Promise\PromiseResolver; |
||
13 | use Cycle\ORM\Promise\ProxyPrinter; |
||
14 | use Cycle\ORM\Promise\Tests\Fixtures; |
||
15 | use Cycle\ORM\Promise\Utils; |
||
16 | use Cycle\Schema; |
||
17 | use PhpParser\PrettyPrinter\Standard; |
||
18 | use PhpParser\PrettyPrinterAbstract; |
||
19 | use Spiral\Core\Container; |
||
20 | use Spiral\Database\Driver\SQLite\SQLiteDriver; |
||
21 | |||
22 | class ProxyPrinterTest extends BaseTest |
||
23 | { |
||
24 | public const DRIVER = 'sqlite'; |
||
25 | |||
26 | /** @var \Spiral\Core\Container */ |
||
27 | private $container; |
||
28 | |||
29 | public function setUp() |
||
30 | { |
||
31 | self::$config = [ |
||
32 | 'debug' => false, |
||
33 | 'strict' => true, |
||
34 | 'benchmark' => false, |
||
35 | 'sqlite' => [ |
||
36 | 'driver' => SQLiteDriver::class, |
||
37 | 'check' => function () { |
||
38 | return !in_array('sqlite', \PDO::getAvailableDrivers()); |
||
39 | }, |
||
40 | 'conn' => 'sqlite::memory:', |
||
41 | 'user' => 'sqlite', |
||
42 | 'pass' => '' |
||
43 | ], |
||
44 | ]; |
||
45 | |||
46 | parent::setUp(); |
||
47 | |||
48 | $this->container = new Container(); |
||
49 | $this->container->bind(ORMInterface::class, $this->orm()); |
||
50 | } |
||
51 | |||
52 | public function testDeclaration(): void |
||
53 | { |
||
54 | $class = Fixtures\Entity::class; |
||
55 | $as = 'EntityProxy' . __LINE__; |
||
56 | |||
57 | $r = new \ReflectionClass($class); |
||
58 | $declaration = Declarations::createFromReflection($r, $as); |
||
59 | $output = $this->make($r, $declaration); |
||
60 | $output = ltrim($output, '<?php'); |
||
61 | |||
62 | $this->assertFalse(class_exists($declaration->class->getFullName())); |
||
63 | |||
64 | eval($output); |
||
65 | |||
66 | $this->assertStringNotContainsString('abstract', $output); |
||
67 | $this->assertStringContainsString(sprintf( |
||
68 | 'class %s extends %s implements %s', |
||
69 | $as, |
||
70 | Utils::shortName($class), |
||
71 | Utils::shortName(PromiseInterface::class) |
||
72 | ), $output); |
||
73 | |||
74 | $proxy = $this->makeProxyObject($class, $declaration->class->getFullName()); |
||
75 | |||
76 | $this->assertInstanceOf($declaration->class->getFullName(), $proxy); |
||
77 | $this->assertInstanceOf($class, $proxy); |
||
78 | $this->assertInstanceOf(PromiseInterface::class, $proxy); |
||
79 | } |
||
80 | |||
81 | /** |
||
82 | * @throws \ReflectionException |
||
83 | */ |
||
84 | public function testSameNamespace(): void |
||
85 | { |
||
86 | $class = Fixtures\Entity::class; |
||
87 | $as = 'EntityProxy' . __LINE__; |
||
88 | |||
89 | $r = new \ReflectionClass($class); |
||
90 | $declaration = Declarations::createFromReflection($r, $as); |
||
91 | $output = $this->make($r, $declaration); |
||
92 | $output = ltrim($output, '<?php'); |
||
93 | |||
94 | $this->assertFalse(class_exists($declaration->class->getFullName())); |
||
95 | |||
96 | eval($output); |
||
97 | |||
98 | $origReflection = new \ReflectionClass($class); |
||
99 | $proxyReflection = new \ReflectionClass($declaration->class->getFullName()); |
||
100 | $this->assertSame($origReflection->getNamespaceName(), $proxyReflection->getNamespaceName()); |
||
101 | } |
||
102 | |||
103 | /** |
||
104 | * @throws \ReflectionException |
||
105 | */ |
||
106 | public function testDifferentNamespace(): void |
||
107 | { |
||
108 | $class = Fixtures\Entity::class; |
||
109 | $as = "\EntityProxy" . __LINE__; |
||
110 | |||
111 | $r = new \ReflectionClass($class); |
||
112 | $declaration = Declarations::createFromReflection($r, $as); |
||
113 | $output = $this->make($r, $declaration); |
||
114 | $output = ltrim($output, '<?php'); |
||
115 | |||
116 | $this->assertFalse(class_exists($declaration->class->getFullName())); |
||
117 | |||
118 | eval($output); |
||
119 | |||
120 | $proxyReflection = new \ReflectionClass($declaration->class->getFullName()); |
||
121 | $this->assertSame('', (string)$proxyReflection->getNamespaceName()); |
||
122 | $this->assertStringNotContainsString('namespace ', $output); |
||
123 | } |
||
124 | |||
125 | /** |
||
126 | * @throws \ReflectionException |
||
127 | */ |
||
128 | public function testUseStmtsInSameNamespace(): void |
||
129 | { |
||
130 | $class = Fixtures\Entity::class; |
||
131 | $as = 'EntityProxy' . __LINE__; |
||
132 | |||
133 | $r = new \ReflectionClass($class); |
||
134 | $declaration = Declarations::createFromReflection($r, $as); |
||
135 | $output = $this->make($r, $declaration); |
||
136 | $output = ltrim($output, '<?php'); |
||
137 | |||
138 | $this->assertFalse(class_exists($declaration->class->getFullName())); |
||
139 | |||
140 | eval($output); |
||
141 | |||
142 | $this->assertSame($this->fetchUseStatements($output), $this->fetchExternalDependencies($declaration->class->getFullName(), [ |
||
143 | PromiseResolver::class, |
||
144 | PromiseInterface::class |
||
145 | ])); |
||
146 | } |
||
147 | |||
148 | /** |
||
149 | * @throws \ReflectionException |
||
150 | */ |
||
151 | public function testUseStmtsInDifferentNamespace(): void |
||
152 | { |
||
153 | $class = Fixtures\Entity::class; |
||
154 | $as = "\EntityProxy" . __LINE__; |
||
155 | |||
156 | $r = new \ReflectionClass($class); |
||
157 | $declaration = Declarations::createFromReflection($r, $as); |
||
158 | $output = $this->make($r, $declaration); |
||
159 | $output = ltrim($output, '<?php'); |
||
160 | |||
161 | $this->assertFalse(class_exists($declaration->class->getFullName())); |
||
162 | |||
163 | eval($output); |
||
164 | |||
165 | $this->assertSame($this->fetchUseStatements($output), $this->fetchExternalDependencies($declaration->class->getFullName(), [ |
||
166 | PromiseResolver::class, |
||
167 | PromiseInterface::class, |
||
168 | $class |
||
169 | ])); |
||
170 | } |
||
171 | |||
172 | private function fetchUseStatements(string $code): array |
||
173 | { |
||
174 | $uses = []; |
||
175 | foreach (explode("\n", $code) as $line) { |
||
176 | if (mb_stripos($line, 'use') !== 0) { |
||
177 | continue; |
||
178 | } |
||
179 | |||
180 | $uses[] = trim(mb_substr($line, 4), " ;\r\n"); |
||
181 | } |
||
182 | |||
183 | sort($uses); |
||
184 | |||
185 | return $uses; |
||
186 | } |
||
187 | |||
188 | /** |
||
189 | * @param string $class |
||
190 | * @param array $types |
||
191 | * |
||
192 | * @return array |
||
193 | * @throws \ReflectionException |
||
194 | */ |
||
195 | private function fetchExternalDependencies(string $class, array $types = []): array |
||
196 | { |
||
197 | $reflection = new \ReflectionClass($class); |
||
198 | |||
199 | foreach ($reflection->getConstructor()->getParameters() as $parameter) { |
||
200 | if (!$parameter->hasType() || $parameter->getType()->isBuiltin()) { |
||
201 | continue; |
||
202 | } |
||
203 | |||
204 | $types[] = $parameter->getType()->getName(); |
||
205 | } |
||
206 | |||
207 | sort($types); |
||
208 | |||
209 | return $types; |
||
210 | } |
||
211 | |||
212 | public function testTraits(): void |
||
213 | { |
||
214 | $r = new \ReflectionClass(Fixtures\EntityWithoutTrait::class); |
||
215 | $this->assertStringNotContainsString(' use ', $this->make($r, Declarations::createFromReflection($r, 'EntityProxy' . __LINE__))); |
||
216 | |||
217 | $r = new \ReflectionClass(Fixtures\EntityWithTrait::class); |
||
218 | $this->assertStringNotContainsString(' use ', $this->make($r, Declarations::createFromReflection($r, 'EntityProxy' . __LINE__))); |
||
219 | } |
||
220 | |||
221 | public function testConstants(): void |
||
222 | { |
||
223 | $r = new \ReflectionClass(Fixtures\EntityWithoutConstants::class); |
||
224 | $this->assertStringNotContainsString(' const ', $this->make($r, Declarations::createFromReflection($r, 'EntityProxy' . __LINE__))); |
||
225 | |||
226 | $r = new \ReflectionClass(Fixtures\EntityWithConstants::class); |
||
227 | $this->assertStringNotContainsString(' const ', $this->make($r, Declarations::createFromReflection($r, 'EntityProxy' . __LINE__))); |
||
228 | } |
||
229 | |||
230 | public function testProperties(): void |
||
231 | { |
||
232 | $class = Fixtures\Entity::class; |
||
233 | $as = 'EntityProxy' . __LINE__; |
||
234 | |||
235 | $r = new \ReflectionClass($class); |
||
236 | $declaration = Declarations::createFromReflection($r, $as); |
||
237 | $output = $this->make($r, $declaration); |
||
238 | $output = ltrim($output, '<?php'); |
||
239 | |||
240 | $this->assertFalse(class_exists($declaration->class->getFullName())); |
||
241 | |||
242 | eval($output); |
||
243 | |||
244 | $reflection = new \ReflectionClass($declaration->class->getFullName()); |
||
245 | |||
246 | /** @var \ReflectionProperty[] $properties */ |
||
247 | $properties = []; |
||
248 | foreach ($reflection->getProperties() as $property) { |
||
249 | if ($property->getDeclaringClass()->getName() !== $declaration->class->getFullName()) { |
||
0 ignored issues
–
show
introduced
by
Loading history...
|
|||
250 | continue; |
||
251 | } |
||
252 | |||
253 | $properties[] = $property; |
||
254 | } |
||
255 | |||
256 | $this->assertCount(1, $properties); |
||
257 | $property = $properties[0]; |
||
258 | $this->assertTrue($property->isPrivate()); |
||
259 | $this->assertFalse($property->isStatic()); |
||
260 | $this->assertStringContainsString('@var PromiseResolver|' . Utils::shortName($class), $property->getDocComment()); |
||
261 | } |
||
262 | |||
263 | /** |
||
264 | * @throws \ReflectionException |
||
265 | */ |
||
266 | public function testHasConstructor(): void |
||
267 | { |
||
268 | $class = Fixtures\EntityWithoutConstructor::class; |
||
269 | $as = 'EntityProxy' . __LINE__; |
||
270 | |||
271 | $r = new \ReflectionClass($class); |
||
272 | $declaration = Declarations::createFromReflection($r, $as); |
||
273 | $output = $this->make($r, $declaration); |
||
274 | $output = ltrim($output, '<?php'); |
||
275 | |||
276 | $this->assertFalse(class_exists($declaration->class->getFullName())); |
||
277 | |||
278 | eval($output); |
||
279 | |||
280 | $reflection = new \ReflectionClass($declaration->class->getFullName()); |
||
281 | $constructor = $reflection->getConstructor(); |
||
282 | $this->assertNotNull($constructor); |
||
283 | |||
284 | $class = Fixtures\EntityWithConstructor::class; |
||
285 | $as = 'EntityProxy' . __LINE__; |
||
286 | |||
287 | $r = new \ReflectionClass($class); |
||
288 | $declaration = Declarations::createFromReflection($r, $as); |
||
289 | $output = $this->make($r, $declaration); |
||
290 | $output = ltrim($output, '<?php'); |
||
291 | |||
292 | $this->assertFalse(class_exists($declaration->class->getFullName())); |
||
293 | |||
294 | eval($output); |
||
295 | |||
296 | $reflection = new \ReflectionClass($declaration->class->getFullName()); |
||
297 | $constructor = $reflection->getConstructor(); |
||
298 | $this->assertNotNull($constructor); |
||
299 | } |
||
300 | |||
301 | public function testNotContainParentConstructor(): void |
||
302 | { |
||
303 | $class = Fixtures\EntityWithoutConstructor::class; |
||
304 | $as = 'EntityProxy' . __LINE__; |
||
305 | |||
306 | $r = new \ReflectionClass($class); |
||
307 | $declaration = Declarations::createFromReflection($r, $as); |
||
308 | $output = $this->make($r, $declaration); |
||
309 | $output = ltrim($output, '<?php'); |
||
310 | |||
311 | $this->assertFalse(class_exists($declaration->class->getFullName())); |
||
312 | |||
313 | eval($output); |
||
314 | |||
315 | $this->assertStringNotContainsString('parent::__construct();', $output); |
||
316 | } |
||
317 | |||
318 | public function testContainParentConstructor(): void |
||
319 | { |
||
320 | $class = Fixtures\EntityWithConstructor::class; |
||
321 | $as = 'EntityProxy' . __LINE__; |
||
322 | |||
323 | $r = new \ReflectionClass($class); |
||
324 | $declaration = Declarations::createFromReflection($r, $as); |
||
325 | $output = $this->make($r, $declaration); |
||
326 | $output = ltrim($output, '<?php'); |
||
327 | |||
328 | $this->assertFalse(class_exists($declaration->class->getFullName())); |
||
329 | |||
330 | eval($output); |
||
331 | |||
332 | $this->assertStringContainsString('parent::__construct();', $output); |
||
333 | } |
||
334 | |||
335 | public function testPromiseMethods(): void |
||
336 | { |
||
337 | $class = Fixtures\Entity::class; |
||
338 | $as = 'EntityProxy' . __LINE__; |
||
339 | |||
340 | $r = new \ReflectionClass($class); |
||
341 | $declaration = Declarations::createFromReflection($r, $as); |
||
342 | $output = $this->make($r, $declaration); |
||
343 | $output = ltrim($output, '<?php'); |
||
344 | |||
345 | $this->assertFalse(class_exists($declaration->class->getFullName())); |
||
346 | |||
347 | eval($output); |
||
348 | |||
349 | $i = new \ReflectionClass(PromiseInterface::class); |
||
350 | foreach ($i->getMethods() as $method) { |
||
351 | $this->assertStringContainsString("public function {$method->getName()}()", $output); |
||
0 ignored issues
–
show
Loading history...
|
|||
352 | } |
||
353 | } |
||
354 | |||
355 | public function testInheritedProperties(): void |
||
356 | { |
||
357 | $class = Fixtures\ChildEntity::class; |
||
358 | $as = 'EntityProxy' . __LINE__; |
||
359 | |||
360 | $reflection = new \ReflectionClass($class); |
||
361 | |||
362 | $r = new \ReflectionClass($class); |
||
363 | $declaration = Declarations::createFromReflection($r, $as); |
||
364 | $output = $this->make($r, $declaration); |
||
365 | $output = ltrim($output, '<?php'); |
||
366 | |||
367 | $this->assertFalse(class_exists($declaration->class->getFullName())); |
||
368 | |||
369 | eval($output); |
||
370 | |||
371 | $sourceProperties = []; |
||
372 | foreach ($reflection->getProperties() as $property) { |
||
373 | $sourceProperties[] = $property->getName(); |
||
374 | } |
||
375 | |||
376 | $properties = []; |
||
377 | foreach ($this->getDeclaration($r)->properties as $property) { |
||
378 | $properties[] = $property; |
||
379 | } |
||
380 | |||
381 | foreach ($sourceProperties as $property) { |
||
382 | $this->assertArrayNotHasKey($property, $properties, "Proxied class contains not expected `{$property}` property"); |
||
383 | $this->assertStringNotContainsString(" $property;", $output); |
||
384 | } |
||
385 | |||
386 | foreach ($properties as $property) { |
||
387 | $this->assertArrayNotHasKey($property, $sourceProperties, "Origin class contains not expected `{$property}` property"); |
||
388 | } |
||
389 | } |
||
390 | |||
391 | public function testInheritedMethods(): void |
||
392 | { |
||
393 | $class = Fixtures\ChildEntity::class; |
||
394 | $as = 'EntityProxy' . __LINE__; |
||
395 | |||
396 | $reflection = new \ReflectionClass($class); |
||
397 | |||
398 | $r = new \ReflectionClass($class); |
||
399 | $declaration = Declarations::createFromReflection($r, $as); |
||
400 | $output = $this->make($r, $declaration); |
||
401 | $output = ltrim($output, '<?php'); |
||
402 | |||
403 | $this->assertFalse(class_exists($declaration->class->getFullName())); |
||
404 | |||
405 | eval($output); |
||
406 | |||
407 | $sourceMethods = []; |
||
408 | |||
409 | //There're only public and protected methods inside |
||
410 | foreach ($reflection->getMethods(\ReflectionMethod::IS_PUBLIC | \ReflectionMethod::IS_PROTECTED) as $method) { |
||
411 | $sourceMethods[$method->getName()] = $method->isPublic() ? 'public' : 'protected'; |
||
0 ignored issues
–
show
Loading history...
|
|||
412 | } |
||
413 | |||
414 | /** @var \PhpParser\Node\Stmt\ClassMethod[] $methods */ |
||
415 | $methods = []; |
||
416 | foreach ($this->getDeclaration($r)->methods as $method) { |
||
417 | $methods[$method->name->name] = $method; |
||
418 | } |
||
419 | |||
420 | foreach ($sourceMethods as $name => $accessor) { |
||
421 | $this->assertArrayHasKey($name, $methods, "Proxy class does not contain expected `{$name}` method"); |
||
422 | |||
423 | if ($accessor === 'public') { |
||
424 | $this->assertTrue($methods[$name]->isPublic(), "Proxied method `{$name}` expected to be public"); |
||
425 | $this->assertStringContainsString("public function {$name}()", $output); |
||
426 | } else { |
||
427 | $this->assertTrue($methods[$name]->isProtected(), "Proxied method `{$name}` expected to be protected"); |
||
428 | $this->assertStringContainsString("protected function {$name}()", $output); |
||
429 | } |
||
430 | } |
||
431 | |||
432 | foreach ($methods as $name => $method) { |
||
433 | $this->assertArrayHasKey($name, $sourceMethods, "Origin class does not contain expected `{$name}` method"); |
||
434 | |||
435 | if ($method->isPublic()) { |
||
436 | $this->assertEquals('public', $sourceMethods[$name], "Proxied method `{$name}` expected to be public"); |
||
437 | } elseif ($method->isProtected()) { |
||
438 | $this->assertEquals('protected', $sourceMethods[$name], "Proxied method `{$name}` expected to be public"); |
||
439 | } else { |
||
440 | throw new \UnexpectedValueException("\"{$method->name->toString()}\" method not found"); |
||
441 | } |
||
442 | } |
||
443 | } |
||
444 | |||
445 | private function getDeclaration(\ReflectionClass $class): Structure |
||
446 | { |
||
447 | return $this->extractor()->extract($class); |
||
448 | } |
||
449 | |||
450 | private function extractor(): Extractor |
||
451 | { |
||
452 | $container = new Container(); |
||
453 | |||
454 | return $container->get(Extractor::class); |
||
455 | } |
||
456 | |||
457 | /** |
||
458 | * @param string $className |
||
459 | * @param string $proxyFullName |
||
460 | * |
||
461 | * @return object |
||
462 | */ |
||
463 | private function makeProxyObject(string $className, string $proxyFullName) |
||
464 | { |
||
465 | return $this->container->make($proxyFullName, ['role' => $className, 'scope' => []]); |
||
466 | } |
||
467 | |||
468 | private function make(\ReflectionClass $reflection, Declarations $declaration): string |
||
469 | { |
||
470 | return $this->proxyCreator()->make($reflection, $declaration); |
||
471 | } |
||
472 | |||
473 | private function proxyCreator(): ProxyPrinter |
||
474 | { |
||
475 | $container = new Container(); |
||
476 | $container->bind(PrettyPrinterAbstract::class, Standard::class); |
||
477 | |||
478 | return $container->get(ProxyPrinter::class); |
||
479 | } |
||
480 | |||
481 | private function orm(): ORMInterface |
||
482 | { |
||
483 | $schema = (new Schema\Compiler())->compile(new Schema\Registry($this->dbal), [ |
||
484 | new Entities($this->locator), |
||
485 | new Schema\Generator\ResetTables(), |
||
486 | new Schema\Generator\GenerateRelations(), |
||
487 | new Schema\Generator\ValidateEntities(), |
||
488 | new Schema\Generator\RenderTables(), |
||
489 | new Schema\Generator\RenderRelations(), |
||
490 | new Schema\Generator\SyncTables(), |
||
491 | new Schema\Generator\GenerateTypecast(), |
||
492 | ]); |
||
493 | |||
494 | return $this->withSchema(new \Cycle\ORM\Schema($schema)); |
||
495 | } |
||
496 | } |