I think the only way of such refactoring is to create your custom code. It doesn't seem to difficulte to create one, but obviously, it can cause more problems than it will solve. If the project doesn't have a quite inclusive test suite, I think it will be way more safer to make it manually. Also, it will be a great idea to test your automation code before you mess with your real code. This means long time to spend. Nevertheless, the code down below is an idead of how it can be done. Finally, I think implementing typehint instead of PHPDocs will make your app nicer and safer, but of course, your choise.
class Refactor
{
public function handle(string $path)
{
$this->refactorFiles($this->listAllFiles($path));
}
public function listAllFiles(string $path): array
{
$files = [];
foreach ($this->listFolderContent($path) as $item) {
$pointer = $path . DIRECTORY_SEPARATOR . $item;
if (is_dir($pointer)) {
$files = [...$files, ...$this->listAllFiles($pointer)];
} else {
$files[] = $pointer;
}
}
return $files;
}
private function listFolderContent(string $path)
{
return array_diff(scandir($path), array_merge(['.', '..']));
}
private function refactorFiles(array $files): void
{
array_map(fn ($file) => $this->refactorFile($file), $files);
}
private function refactorFile(string $file): void
{
$content = file($file);
$newContent = [];
foreach ($content as $i => $line) {
if (!str_contains($line, 'function')) {
$newContent[] = $line;
continue;
};
$firstIndex = $this->indexOfTheLineWhereTheCommentStarts($content, $i);
$comments = $this->getComment($content, $firstIndex, $i - 1);
$newContent = [...$newContent, ...$this->buildDocBlock($comments, $line)];
}
file_put_contents($file, implode('', $newContent));
}
private function indexOfTheLineWhereTheCommentStarts(array $content, int $index)
{
$prevIndex = $index - 1;
$prevLine = $content[$prevIndex];
while (str_contains($prevLine, '//')) {
$prevIndex = $prevIndex - 1;
$prevLine = $content[$prevIndex];
}
return $prevIndex;
}
private function getComment(array $content, int $firstIndex, int $lastIndex): array
{
return array_map(
fn ($commentLine) => trim(str_replace('//', '', $commentLine)),
array_slice($content, $firstIndex, $lastIndex - $firstIndex + 1)
);
}
private function buildDocBlock(array $comments, string $line): array
{
return array_map(
fn ($line) => " $line",
[
'/**',
...$this->convertCommentLines($comments),
' *',
...$this->setParameters($line),
' * @return mixed',
' */'
]
);
}
private function convertCommentLines(array $comments): array
{
return array_map(fn ($line) => ' * ' . trim(str_replace('//', '', $line)), $comments);
}
private function setParameters(string $line): array
{
return array_map(
fn ($param) => ' * @param ' . trim(explode('=', $param)[0]),
$this->isolateParams($line)
);
}
private function isolateParams(string $line): array
{
return explode(',', explode(')', explode('(', $line)[1] ?? '')[0]);
}
}
function foo()and the type/**and press enter, the block will be generated and you only have to copy the text from//foobarto the new block. Automation can break things or do the wrong stuff.*/before the definiton line. I will test it now.