1 | <?php |
||||
2 | |||||
3 | namespace Flawlol\FacadeIdeHelper\Service; |
||||
4 | |||||
5 | use Flawlol\Facade\Abstract\Facade; |
||||
6 | use Flawlol\FacadeIdeHelper\Interface\FacadeHelperGeneratorInterface; |
||||
7 | use ReflectionParameter; |
||||
8 | use SplFileObject; |
||||
9 | use Symfony\Component\DependencyInjection\ContainerInterface; |
||||
10 | use Symfony\Component\Finder\Finder; |
||||
11 | |||||
12 | /** |
||||
13 | * Class FacadeHelperGenerator. |
||||
14 | * |
||||
15 | * This class is responsible for generating facade helper files for IDEs. |
||||
16 | * |
||||
17 | * @author Flawlol - Norbert Kecső |
||||
18 | */ |
||||
19 | final class FacadeHelperGenerator implements FacadeHelperGeneratorInterface |
||||
20 | { |
||||
21 | /** |
||||
22 | * @var string The current namespace being processed. |
||||
23 | */ |
||||
24 | private string $currentNamespace = ''; |
||||
25 | |||||
26 | /** |
||||
27 | * FacadeHelperGenerator constructor. |
||||
28 | * |
||||
29 | * @param ContainerInterface $container The container interface for dependency injection. |
||||
30 | */ |
||||
31 | public function __construct(private ContainerInterface $container) |
||||
32 | { |
||||
33 | } |
||||
34 | |||||
35 | /** |
||||
36 | * Generate the facade helper file. |
||||
37 | * |
||||
38 | * This method scans the source directory for PHP files, identifies classes that extend the Facade class, |
||||
39 | * and writes their method signatures to a helper file for IDE autocompletion. |
||||
40 | */ |
||||
41 | public function generate(): void |
||||
42 | { |
||||
43 | $projectDir = $this->container->getParameter('kernel.project_dir'); |
||||
0 ignored issues
–
show
Bug
introduced
by
![]() |
|||||
44 | $srcDir = $projectDir.'/src'; |
||||
45 | |||||
46 | $finder = new Finder(); |
||||
47 | $finder->files()->in($srcDir)->name('*.php'); |
||||
48 | |||||
49 | $helperFile = new SplFileObject('_ide-helper.php', 'w'); |
||||
50 | $helperFile->fwrite("<?php\n"); |
||||
51 | $helperFile->fwrite("\n"); |
||||
52 | |||||
53 | foreach ($finder as $file) { |
||||
54 | $className = $this->getClassNameFromFile($file->getRealPath()); |
||||
55 | |||||
56 | if (!class_exists($className)) { |
||||
0 ignored issues
–
show
It seems like
$className can also be of type null ; however, parameter $class of class_exists() does only seem to accept string , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
57 | continue; |
||||
58 | } |
||||
59 | |||||
60 | $reflectionClass = new \ReflectionClass($className); |
||||
61 | |||||
62 | if ($reflectionClass->isAbstract() || $reflectionClass->isInterface()) { |
||||
63 | continue; |
||||
64 | } |
||||
65 | |||||
66 | $object = $reflectionClass->newInstanceWithoutConstructor(); |
||||
67 | |||||
68 | if ($this->isInstanceOfWithoutInitializing($object)) { |
||||
69 | $this->writeNamespaceAndClass($helperFile, $reflectionClass, $object); |
||||
70 | } |
||||
71 | } |
||||
72 | |||||
73 | if ($this->currentNamespace !== '') { |
||||
74 | $helperFile->fwrite("}\n\n"); |
||||
75 | } |
||||
76 | } |
||||
77 | |||||
78 | /** |
||||
79 | * Get the fully qualified class name from a file. |
||||
80 | * |
||||
81 | * @param string $filePath The path to the file. |
||||
82 | * |
||||
83 | * @return string|null The fully qualified class name, or null if not found. |
||||
84 | */ |
||||
85 | private function getClassNameFromFile(string $filePath): ?string |
||||
86 | { |
||||
87 | $contents = file_get_contents($filePath); |
||||
88 | $namespace = ''; |
||||
89 | |||||
90 | if (preg_match('/namespace\s+(.+?);/', $contents, $matches)) { |
||||
91 | $namespace = $matches[1]; |
||||
92 | } |
||||
93 | |||||
94 | if (preg_match('/class\s+(\w+)/', $contents, $matches)) { |
||||
95 | return $namespace ? $namespace.'\\'.$matches[1] : $matches[1]; |
||||
96 | } |
||||
97 | |||||
98 | return null; |
||||
99 | } |
||||
100 | |||||
101 | /** |
||||
102 | * Check if a class is an instance of the Facade class without initializing it. |
||||
103 | * |
||||
104 | * @param object|null $className The class to check. |
||||
105 | * |
||||
106 | * @return bool True if the class is an instance of the Facade class, false otherwise. |
||||
107 | */ |
||||
108 | private function isInstanceOfWithoutInitializing(object $className = null): bool |
||||
109 | { |
||||
110 | return is_subclass_of($className, Facade::class) || in_array(Facade::class, class_implements($className)); |
||||
111 | } |
||||
112 | |||||
113 | /** |
||||
114 | * Write the namespace and class structure to the helper file. |
||||
115 | * |
||||
116 | * @param SplFileObject $helperFile The helper file object. |
||||
117 | * @param \ReflectionClass $reflectionClass The reflection class object. |
||||
118 | * @param object $object The class object. |
||||
119 | */ |
||||
120 | private function writeNamespaceAndClass(SplFileObject $helperFile, \ReflectionClass $reflectionClass, $object): void |
||||
121 | { |
||||
122 | $namespace = $reflectionClass->getNamespaceName(); |
||||
123 | |||||
124 | if ($namespace !== $this->currentNamespace) { |
||||
125 | if ($this->currentNamespace !== '') { |
||||
126 | $helperFile->fwrite("}\n\n"); |
||||
127 | } |
||||
128 | |||||
129 | $this->currentNamespace = $namespace; |
||||
130 | $helperFile->fwrite("namespace {$namespace} {\n"); |
||||
131 | $helperFile->fwrite("\n"); |
||||
132 | } |
||||
133 | |||||
134 | $helperFile->fwrite(" class {$reflectionClass->getShortName()}\n"); |
||||
135 | $helperFile->fwrite(" {\n"); |
||||
136 | |||||
137 | $service = $object::getFacadeAccessor(); |
||||
138 | $serviceInstance = $this->container->get($service); |
||||
139 | $serviceInstanceReflection = new \ReflectionClass($serviceInstance); |
||||
140 | $methods = $serviceInstanceReflection->getMethods(); |
||||
141 | |||||
142 | foreach ($methods as $method) { |
||||
143 | if ($method->isConstructor()) { |
||||
144 | continue; |
||||
145 | } |
||||
146 | |||||
147 | if ($method->isPublic()) { |
||||
148 | $this->writeMethod($helperFile, $method, $serviceInstanceReflection); |
||||
149 | } |
||||
150 | } |
||||
151 | |||||
152 | $helperFile->fwrite(" }\n"); |
||||
153 | } |
||||
154 | |||||
155 | /** |
||||
156 | * Write the method signature and docblock to the helper file. |
||||
157 | * |
||||
158 | * @param SplFileObject $helperFile The helper file object. |
||||
159 | * @param \ReflectionMethod $method The reflection method object. |
||||
160 | * @param \ReflectionClass $serviceInstanceReflection The reflection class object of the service instance. |
||||
161 | */ |
||||
162 | private function writeMethod(SplFileObject $helperFile, \ReflectionMethod $method, \ReflectionClass $serviceInstanceReflection): void |
||||
163 | { |
||||
164 | $params = []; |
||||
165 | |||||
166 | foreach ($method->getParameters() as $param) { |
||||
167 | $paramType = $param->getType(); |
||||
168 | $type = $paramType ? $paramType->getName().' ' : ''; |
||||
0 ignored issues
–
show
The method
getName() does not exist on ReflectionType . It seems like you code against a sub-type of ReflectionType such as ReflectionNamedType .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
169 | $defaultValue = $param->isOptional() ? ' = '.var_export($param->getDefaultValue(), true) : ''; |
||||
170 | $params[] = $type.'$'.$param->getName().$defaultValue; |
||||
171 | } |
||||
172 | |||||
173 | $paramsStringOriginal = implode(', ', $params); |
||||
174 | |||||
175 | $paramsVariable = $params ? '$'.implode( |
||||
176 | ', $', |
||||
177 | array_map( |
||||
178 | static fn (ReflectionParameter $param): string => $param->getName(), |
||||
179 | $method->getParameters() |
||||
180 | ) |
||||
181 | ) : ''; |
||||
182 | |||||
183 | $returnType = $method->getReturnType(); |
||||
184 | $returnTypeString = $returnType ? $returnType->getName() : 'mixed'; |
||||
185 | |||||
186 | if ($returnTypeString === 'self') { |
||||
187 | $returnTypeString = '\\'.$serviceInstanceReflection->getName(); |
||||
188 | } |
||||
189 | |||||
190 | $params = []; |
||||
191 | |||||
192 | foreach ($method->getParameters() as $param) { |
||||
193 | $paramType = $param->getType(); |
||||
194 | $type = $paramType ? $paramType->getName().' ' : ''; |
||||
195 | $params[] = "@param {$type}\${$param->getName()}"; |
||||
196 | } |
||||
197 | |||||
198 | $paramsString = implode("\n * ", $params); |
||||
199 | |||||
200 | $helperFile->fwrite(" /**\n"); |
||||
201 | |||||
202 | if ($paramsString) { |
||||
203 | $helperFile->fwrite(" * {$paramsString}\n"); |
||||
204 | } |
||||
205 | $helperFile->fwrite(" * @return {$returnTypeString}\n"); |
||||
206 | $helperFile->fwrite(" */\n"); |
||||
207 | |||||
208 | $helperFile->fwrite(" public static function {$method->getName()}($paramsStringOriginal): $returnTypeString\n"); |
||||
209 | $helperFile->fwrite(" {\n"); |
||||
210 | $helperFile->fwrite(" /** @var \\{$serviceInstanceReflection->getName()} \$instance */\n"); |
||||
211 | $helperFile->fwrite(" return \$instance->{$method->getName()}($paramsVariable);\n"); |
||||
212 | $helperFile->fwrite(" }\n"); |
||||
213 | } |
||||
214 | } |
||||
215 |