| 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
Loading history...
|
|||||
| 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
Loading history...
|
|||||
| 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
Loading history...
|
|||||
| 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 |