Skip to content
Snippets Groups Projects
Commit 7de23c1b authored by Jan-Hendrik Willms's avatar Jan-Hendrik Willms
Browse files

prepare method definitions for trails controllers

parent ffc91490
No related branches found
No related tags found
No related merge requests found
...@@ -12,7 +12,7 @@ ...@@ -12,7 +12,7 @@
* the License, or (at your option) any later version. * the License, or (at your option) any later version.
*/ */
class AuthenticatedController extends StudipController abstract class AuthenticatedController extends StudipController
{ {
protected $with_session = true; //we do need to have a session for this controller protected $with_session = true; //we do need to have a session for this controller
protected $allow_nobody = false; //nobody is not allowed and always gets a login-screen protected $allow_nobody = false; //nobody is not allowed and always gets a login-screen
......
...@@ -29,7 +29,7 @@ final class DescribeModels extends AbstractCommand ...@@ -29,7 +29,7 @@ final class DescribeModels extends AbstractCommand
protected function configure(): void protected function configure(): void
{ {
$this->setDescription('Describe models'); $this->setDescription('Describe models');
$this->setHelp('This command will add neccessary @property annotations to SimpleORMap classes in a folder'); $this->setHelp('This command will add neccessary @property and @method annotations to SimpleORMap classes in a folder');
$this->addArgument( $this->addArgument(
'folder', 'folder',
......
<?php
namespace Studip\Cli\Commands\Trails;
use SimpleORMapCollection;
use Studip\Cli\Commands\AbstractCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
final class DescribeControllers extends AbstractCommand
{
protected static $defaultName = 'trails:describe';
private $progress;
protected function configure(): void
{
$this->setDescription('Describe controllers');
$this->setHelp('This command will add neccessary @property annotations to SimpleORMap classes in a folder');
$this->addArgument(
'folder',
InputArgument::OPTIONAL,
'Folder to scan (will default to app/controllers)',
'app/controllers'
);
$this->addOption(
'recursive',
'r',
InputOption::VALUE_NEGATABLE,
'Scan into subfolders recursively'
);
$this->addOption(
'bootstrap',
'b',
InputOption::VALUE_OPTIONAL,
'Execute bootstrap file before scanning folder'
);
}
public function execute(InputInterface $input, OutputInterface $output)
{
$bootstrap = $input->getOption('bootstrap');
if ($bootstrap) {
if (!file_exists($bootstrap)) {
throw new \Exception("Invalid bootstrap file {$bootstrap} provided");
}
require_once $bootstrap;
}
$recursive = $input->getOption('recursive') ?? false;
$folder = $input->getArgument('folder');
$iterator = $this->getFolderIterator($folder, $recursive, ['php']);
$this->progress = new ProgressBar($output, iterator_count($iterator));
$this->progress->start();
foreach ($iterator as $file) {
$filename = $file->getFilename();
if (!is_writable($file->getRealPath())) {
$this->outputForFile(
$output,
'<comment>Skipping not writable file ' . $this->relativeFilePath($file->getPathname()) . '</comment>'
);
continue;
}
$class_name = $this->getClassNameFromFile($file->getPathname());
if ($class_name === null) {
$this->outputForFile(
$output,
"Could not determine class name for file {$filename} ({$file->getPathName()})",
OutputInterface::VERBOSITY_VERBOSE
);
continue;
}
require_once $file->getPathName();
if (!class_exists($class_name) || !is_subclass_of($class_name, \Trails_Controller::class)) {
$this->outputForFile(
$output,
"Skipping invalid class file {$filename} (class {$class_name} / file {$file->getPathName()})",
OutputInterface::VERBOSITY_VERBOSE
);
continue;
}
try {
$reflection = new \ReflectionClass($class_name);
} catch (\Error $e) {
$this->outputForFile(
$output,
"<error>Could not get reflection for class {$class_name} ({$e->getMessage()})</error>"
);
continue;
}
if ($reflection->isAbstract()) {
$this->outputForFile(
$output,
"Skipping abstract class {$class_name}",
OutputInterface::VERBOSITY_VERBOSE
);
continue;
}
if (false && $this->updateDocBlockOfClass($reflection, $properties, $methods)) {
$this->outputForFile(
$output,
'<info>Updated ' . $this->relativeFilePath($file->getPathname()) . '</info>'
);
} else {
$this->outputForFile(
$output,
'No changes in ' . $this->relativeFilePath($file->getPathname()),
OutputInterface::VERBOSITY_VERBOSE
);
}
}
$this->progress->clear();
return Command::SUCCESS;
}
private function outputForFile($output, ...$args)
{
$this->progress->advance();
$this->progress->clear();
$output->writeln(...$args);
$this->progress->display();
}
private function getPHPType($info)
{
if (preg_match('/^(?:tiny|small|medium|big)?int(?:eger)?/iS', $info['type'])) {
return 'int';
}
if (preg_match('/^(?:decimal|double|float|numeric)/iS', $info['type'])) {
return 'float';
}
if (preg_match('/^bool(?:ean)?/iS', $info['type'])) {
return 'bool';
}
return 'string';
}
private function updateDocBlockOfClass(\ReflectionClass $reflection, array $properties, array $methods): bool
{
$has_docblock = (bool) $reflection->getDocComment();
$docblock = $reflection->getDocComment() ?: $this->getDefaultDocblock();
$docblock_lines = array_map('rtrim', explode("\n", $docblock));
$properties_started = false;
$docblock_lines = array_filter($docblock_lines, function ($line) use (&$properties_started) {
$line = ltrim($line, '* ');
if ($properties_started) {
return $line === '/';
}
$properties_started = strpos($line, '@property ') === 0;
return !$properties_started;
});
$docblock_lines = array_reverse($docblock_lines);
while ($docblock_lines[1] === ' *') {
array_splice($docblock_lines, 1, 1);
}
$docblock_lines = array_reverse($docblock_lines);
$properties = array_map(function ($variable, $property) {
return " * @property {$property['type']} \${$variable} {$property['description']}";
}, array_keys($properties), array_values($properties));
array_unshift($properties, ' *');
array_splice($docblock_lines, -1, 0, $properties);
$methods = array_map(function ($method) {
return " * @method {$method}";
}, $methods);
array_unshift($methods, ' *');
array_splice($docblock_lines, -1, 0, $methods);
$new_docblock = implode("\n", $docblock_lines);
if ($docblock === $new_docblock) {
return false;
}
$contents = file_get_contents($reflection->getFileName());
if ($has_docblock) {
$contents = str_replace($docblock, $new_docblock, $contents);
} else {
$contents = preg_replace(
'/^class/m',
$new_docblock . "\nclass",
$contents,
1
);
}
file_put_contents($reflection->getFileName(), $contents);
return true;
}
private function getDefaultDocBlock(): string
{
return implode("\n", [
'/**',
' * @license GPL2 or any later version',
' */',
]);
}
/**
* @see https://stackoverflow.com/a/14250011
*/
private function getClassNameFromFile(string $filename, string $expected_class = null): ?string
{
$code = file_get_contents($filename);
$tokens = token_get_all($code);
$namespace = '';
for ($i = 0, $l = count($tokens); $i < $l; $i += 1) {
if ($tokens[$i][0] === T_NAMESPACE) {
for ($j= $i + 1; $j < $l; $j += 1) {
if ($tokens[$j][0] === T_STRING) {
$namespace .= "\\" . $tokens[$j][1];
} elseif ($tokens[$j] === '{' || $tokens[$j] === ';') {
break;
}
}
}
if ($tokens[$i][0] === T_CLASS) {
for ($j = $i + 1; $j < $l; $j += 1) {
if ($tokens[$j] === '{') {
return ltrim($namespace . "\\" . $tokens[$i + 2][1], "\\");
}
}
}
}
return null;
}
private function makeMethods(\ReflectionClass $reflection, string $field): array
{
$result = [];
foreach (self::METHODS_TEMPLATES as $name => $template) {
$method = sprintf($name, $field);
if (!$reflection->hasMethod($method)) {
$result[] = sprintf($template, $field);
}
}
return $result;
}
}
...@@ -52,6 +52,7 @@ $commands = [ ...@@ -52,6 +52,7 @@ $commands = [
Commands\Plugins\I18N\I18NCompile::class, Commands\Plugins\I18N\I18NCompile::class,
Commands\Resources\UpdateBookingIntervals::class, Commands\Resources\UpdateBookingIntervals::class,
Commands\SORM\DescribeModels::class, Commands\SORM\DescribeModels::class,
Commands\Trails\DescribeControllers::class,
Commands\Users\UserDelete::class, Commands\Users\UserDelete::class,
Commands\Users\UserDelete::class, Commands\Users\UserDelete::class,
]; ];
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment