$class */ function makeTypehint(string $namespaceName, \ReflectionClass $class) : string{ return $class->getNamespaceName() === $namespaceName ? $class->getShortName() : '\\' . $class->getName(); } /** * @param object[] $members * @phpstan-param array $members * @phpstan-param array $overloadedMembers */ function generateMethodAnnotations(string $namespaceName, array $members, array $overloadedMembers) : string{ $selfName = basename(__FILE__); $lines = ["/**"]; $lines[] = " * This doc-block is generated automatically, do not modify it manually."; $lines[] = " * This must be regenerated whenever registry members are added, removed or changed."; $lines[] = " * @see build/$selfName"; $lines[] = " * @generate-registry-docblock"; $lines[] = " *"; static $lineTmpl = " * @method static %2\$s %s()"; $memberLines = []; foreach(Utils::stringifyKeys($members) as $name => $member){ $reflect = new \ReflectionClass($member); while($reflect !== false && $reflect->isAnonymous()){ $reflect = $reflect->getParentClass(); } if($reflect === false){ $typehint = "object"; }else{ $typehint = makeTypehint($namespaceName, $reflect); } $accessor = mb_strtoupper($name); $memberLines[$accessor] = sprintf($lineTmpl, $accessor, $typehint); } foreach(Utils::stringifyKeys($overloadedMembers) as $baseName => $member){ $accessor = mb_strtoupper($baseName); $returnTypehint = makeTypehint($namespaceName, new \ReflectionClass($member->memberClass)); $enumReflect = new \ReflectionClass($member->enumClass); $paramTypehint = makeTypehint($namespaceName, $enumReflect); $memberLines[] = sprintf(" * @method static %s %s(%s \$%s)", $returnTypehint, $accessor, $paramTypehint, lcfirst($enumReflect->getShortName())); } ksort($memberLines, SORT_STRING); foreach($memberLines as $line){ $lines[] = $line; } $lines[] = " */"; return implode("\n", $lines); } function processFile(string $file) : void{ $contents = file_get_contents($file); if($contents === false){ throw new \RuntimeException("Failed to get contents of $file"); } if(preg_match("/(*ANYCRLF)^namespace (.+);$/m", $contents, $matches) !== 1 || preg_match('/(*ANYCRLF)^((final|abstract)\s+)?class /m', $contents) !== 1){ return; } $shortClassName = basename($file, ".php"); $className = $matches[1] . "\\" . $shortClassName; if(!class_exists($className)){ return; } $reflect = new \ReflectionClass($className); $docComment = $reflect->getDocComment(); if($docComment === false || preg_match("/(*ANYCRLF)^\s*\*\s*@generate-registry-docblock$/m", $docComment) !== 1){ return; } echo "Found registry in $file\n"; $replacement = generateMethodAnnotations($matches[1], $className::getAll(), $className::getAllOverloaded()); $newContents = str_replace($docComment, $replacement, $contents); if($newContents !== $contents){ echo "Writing changed file $file\n"; file_put_contents($file, $newContents); }else{ echo "No changes made to file $file\n"; } } require dirname(__DIR__) . '/vendor/autoload.php'; if(is_dir($argv[1])){ /** @var string $file */ foreach(new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($argv[1], \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::CURRENT_AS_PATHNAME)) as $file){ if(!str_ends_with($file, ".php")){ continue; } processFile($file); } }else{ processFile($argv[1]); }