|
12 | 12 | namespace Translation\Extractor\Visitor\Php\Symfony; |
13 | 13 |
|
14 | 14 | use PhpParser\Node; |
15 | | -use PhpParser\Node\Stmt; |
| 15 | +use PhpParser\Node\Stmt\Class_; |
| 16 | +use PhpParser\NodeTraverser; |
| 17 | +use PhpParser\NodeVisitor\NameResolver; |
| 18 | +use PhpParser\ParserFactory; |
| 19 | +use PhpParser\PhpVersion; |
16 | 20 |
|
17 | 21 | trait FormTrait |
18 | 22 | { |
19 | 23 | private bool $isFormType = false; |
| 24 | + private array $classMap = []; |
| 25 | + private string $symfonyInterface = 'FormTypeInterface'; |
20 | 26 |
|
21 | | - /** |
22 | | - * Check if this node is a form type. |
23 | | - */ |
24 | | - private function isFormType(Node $node): bool |
| 27 | + protected function isFormType(Node $node): bool |
25 | 28 | { |
26 | | - // only Traverse *Type |
27 | | - if ($node instanceof Stmt\Class_) { |
28 | | - $this->isFormType = 'Type' === substr($node->name, -4); |
| 29 | + if (!$node instanceof Class_) { |
| 30 | + return $this->isFormType; |
| 31 | + } |
| 32 | + |
| 33 | + $allInterfaces = $this->getAllInterfacesFromNode($node); |
| 34 | + |
| 35 | + foreach ($allInterfaces as $interface) { |
| 36 | + if ($interface === $this->symfonyInterface |
| 37 | + || str_ends_with($interface, '\\'.$this->symfonyInterface)) { |
| 38 | + $this->isFormType = true; |
| 39 | + } |
29 | 40 | } |
30 | 41 |
|
31 | 42 | return $this->isFormType; |
32 | 43 | } |
| 44 | + |
| 45 | + protected function getAllInterfacesFromNode(Class_ $node): array |
| 46 | + { |
| 47 | + $interfaces = []; |
| 48 | + |
| 49 | + foreach ($node->implements as $interface) { |
| 50 | + $interfaces[] = $interface->toString(); |
| 51 | + } |
| 52 | + |
| 53 | + if ($node->extends) { |
| 54 | + $parentFqcn = $node->extends->toString(); |
| 55 | + |
| 56 | + $parentInterfaces = $this->loadParentInterfaces($parentFqcn); |
| 57 | + $interfaces = array_merge($interfaces, $parentInterfaces); |
| 58 | + } |
| 59 | + |
| 60 | + return array_unique($interfaces); |
| 61 | + } |
| 62 | + |
| 63 | + protected function loadParentInterfaces(string $parentFqcn): array |
| 64 | + { |
| 65 | + $interfaces = []; |
| 66 | + |
| 67 | + static $loading = []; |
| 68 | + if (isset($loading[$parentFqcn])) { |
| 69 | + return []; |
| 70 | + } |
| 71 | + $loading[$parentFqcn] = true; |
| 72 | + |
| 73 | + try { |
| 74 | + $filePath = $this->findClassFile($parentFqcn); |
| 75 | + |
| 76 | + if (!$filePath || !file_exists($filePath)) { |
| 77 | + unset($loading[$parentFqcn]); |
| 78 | + |
| 79 | + return []; |
| 80 | + } |
| 81 | + |
| 82 | + $parser = (new ParserFactory())->createForVersion(PhpVersion::fromString('8.1')); |
| 83 | + $code = file_get_contents($filePath); |
| 84 | + $stmts = $parser->parse($code); |
| 85 | + |
| 86 | + $traverser = new NodeTraverser(); |
| 87 | + $traverser->addVisitor(new NameResolver()); |
| 88 | + $stmts = $traverser->traverse($stmts); |
| 89 | + |
| 90 | + foreach ($stmts as $stmt) { |
| 91 | + if ($stmt instanceof Node\Stmt\Namespace_) { |
| 92 | + foreach ($stmt->stmts as $subStmt) { |
| 93 | + if ($subStmt instanceof Class_) { |
| 94 | + $interfaces = array_merge( |
| 95 | + $interfaces, |
| 96 | + $this->getAllInterfacesFromNode($subStmt) |
| 97 | + ); |
| 98 | + } |
| 99 | + } |
| 100 | + } elseif ($stmt instanceof Class_) { |
| 101 | + $interfaces = array_merge( |
| 102 | + $interfaces, |
| 103 | + $this->getAllInterfacesFromNode($stmt) |
| 104 | + ); |
| 105 | + } |
| 106 | + } |
| 107 | + } catch (\Exception $e) { |
| 108 | + } |
| 109 | + |
| 110 | + unset($loading[$parentFqcn]); |
| 111 | + |
| 112 | + return $interfaces; |
| 113 | + } |
| 114 | + |
| 115 | + private function findClassFile(string $fqcn): ?string |
| 116 | + { |
| 117 | + $autoloadFile = __DIR__.'/../../../../vendor/autoload.php'; |
| 118 | + |
| 119 | + if (!file_exists($autoloadFile)) { |
| 120 | + return null; |
| 121 | + } |
| 122 | + |
| 123 | + $loader = require $autoloadFile; |
| 124 | + |
| 125 | + return $loader->findFile($fqcn) ?: null; |
| 126 | + } |
33 | 127 | } |
0 commit comments