Skip to content
Snippets Groups Projects
Commit 9e8c92f4 authored by David Siegfried's avatar David Siegfried Committed by Jan-Hendrik Willms
Browse files

add migration-maker, closes #3806

Closes #3806

Merge request studip/studip!3358
parent c5fd2ec6
No related branches found
No related tags found
No related merge requests found
...@@ -66,6 +66,8 @@ ...@@ -66,6 +66,8 @@
- Die Evaluationen wurden ausgebaut. Stattdessen sollte man nun die neuen Fragebögen verwenden ([Issue #3787]https://gitlab.studip.de/studip/studip/-/issues/3787) - Die Evaluationen wurden ausgebaut. Stattdessen sollte man nun die neuen Fragebögen verwenden ([Issue #3787]https://gitlab.studip.de/studip/studip/-/issues/3787)
- Die Klassen `DbView`, `DbSnapshot` und die zugehörigen Dateien in `lib/dbviews` wurden ausgebaut. ([Issue #4390](https://gitlab.studip.de/studip/studip/-/issues/4390)) - Die Klassen `DbView`, `DbSnapshot` und die zugehörigen Dateien in `lib/dbviews` wurden ausgebaut. ([Issue #4390](https://gitlab.studip.de/studip/studip/-/issues/4390))
- Als Ersatz dienen Datenbankabfragen mittels der `DBManager`-Klasse oder mittels `SimpleORMap`-Modellen. - Als Ersatz dienen Datenbankabfragen mittels der `DBManager`-Klasse oder mittels `SimpleORMap`-Modellen.
- Es wurden zwei neue CLI-Kommandos hinzugefügt, womit man Klassenrümpfe für SORM-Models und Migrationen erstellen kann. Bei den Migrationen wird die Versionsnummer für die jeweilige `domain` automatisch ermittelt.
- `cli/studip make:model` und `cli/studip make:migration`.
## Security related issues ## Security related issues
......
<?php
namespace Studip\Cli\Commands\Make;
use Nette\PhpGenerator\PhpFile;
use Symfony\Component\Console\Command\Command;
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 Migration extends Command
{
private const DEFAULT_BRANCH = '0';
protected static $defaultName = 'make:migration';
protected function configure(): void
{
$this->setDescription('Create a new migration file');
$this->addArgument('name', InputArgument::REQUIRED, 'The name of the migration');
$this->addOption(
'branch',
'b',
InputOption::VALUE_OPTIONAL,
'The branch of the migration file',
self::DEFAULT_BRANCH
);
$this->addOption('domain', 'd', InputOption::VALUE_OPTIONAL, 'The domain of the migration file', 'studip');
$defaultPath = $GLOBALS['STUDIP_BASE_PATH'] . '/db/migrations';
$this->addOption(
'path',
'p',
InputOption::VALUE_OPTIONAL,
'The location where the migration file should be created',
$defaultPath
);
$this->addOption(
'description',
'D',
InputOption::VALUE_OPTIONAL,
'The description for the migration',
''
);
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$branch = $input->getOption('branch');
$domain = $input->getOption('domain');
$name = $input->getArgument('name');
$path = $input->getOption('path');
$description = $input->getOption('description');
$verbose = $input->getOption('verbose');
$version = $this->getNextMigrationVersion($branch, $domain, $path, $verbose);
$filename = $this->createMigrationFile($path, $version, $name, $description);
if ($verbose) {
$output->writeln('Migration file ' . $filename . ' created.');
}
return Command::SUCCESS;
}
private function getNextMigrationVersion(string $branch, string $domain, string $path, bool $verbose): string
{
$version = new \DBSchemaVersion($domain, $branch);
$migrator = new \Migrator($path, $version, $verbose);
$topVersions = $migrator->topVersion(true);
if ($branch === self::DEFAULT_BRANCH) {
$branches = array_keys($topVersions);
usort($branches, 'version_compare');
$branch = array_pop($branches);
}
return sprintf('%s.%s', $branch, isset($topVersions[$branch]) ? $topVersions[$branch] + 1 : 1);
}
private function createMigrationFile(string $path, string $version, string $name, string $description): string
{
if ($description === '') {
$description = '// Add content';
} else {
$description = "return '$description';";
}
$file = new PhpFile();
$class = $file->addClass(str_replace(' ', '', ucwords($name)));
$class
->setFinal()
->setExtends(\Migration::class)
->addComment("Description of class.\nSecond line\n");
$class->addMethod('description')->addBody($description);
$class->addMethod('up')->addBody('// Add content');
$class->addMethod('down')->addBody('// Add content');
$printer = new StudipClassPrinter();
$result = $printer->printFile($file);
$migrationName = $version . '_' . str_replace(' ', '_', lcfirst($name));
$filename = $path . '/' . $migrationName . '.php';
file_put_contents($filename, $result);
return $filename;
}
}
<?php
namespace Studip\Cli\Commands\Make;
use Nette\PhpGenerator\PhpFile;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ChoiceQuestion;
final class Model extends Command
{
protected static $defaultName = 'make:model';
protected function configure(): void
{
$this->setDescription('Create a sorm-model file');
$this->addArgument('name', InputArgument::REQUIRED, 'The name of the sorm-model');
$this->addArgument('db-table', InputArgument::OPTIONAL, 'The name of the related db-table');
$this->addOption('namespace', 's', InputOption::VALUE_OPTIONAL, 'Namespace', '');
$defaultPath = $GLOBALS['STUDIP_BASE_PATH'] . '/lib/models';
$this->addOption(
'path',
'p',
InputOption::VALUE_OPTIONAL,
'The location where the model file should be created',
$defaultPath
);
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$namespace = $input->getOption('namespace');
$name = $input->getArgument('name');
$dbTable = $input->getArgument('db-table');
$path = $input->getOption('path');
$verbose = $input->getOption('verbose');
$filename = $this->createModelFile(
$path,
$name,
$input,
$output,
$dbTable,
$namespace
);
if ($verbose) {
$output->writeln('Model file ' . $filename . ' created.');
}
return Command::SUCCESS;
}
private function createModelFile(
string $path,
string $name,
InputInterface $input,
OutputInterface $output,
string $dbTable = null,
string $namespace = null,
): string
{
if (!$dbTable) {
$dbTable = strtosnakecase($name);
}
$file = new PhpFile();
$className = str_replace(' ', '', ucwords($name));
if ($namespace) {
$className = ucfirst($namespace) . '\\' . $className;
}
$class = $file->addClass($className);
$class->setExtends(\SimpleORMap::class);
$class->addComment(ucfirst($name) . '.php');
$class->addComment('model class for table ' . $dbTable);
$method = $class->addMethod('configure')
->setStatic()
->setProtected();
$method->addBody(sprintf('$config[\'db_table\'] = \'%s\';', $dbTable));
$method->addBody('parent::configure($config);');
$method->addParameter('config', []);
$printer = new StudipClassPrinter();
$result = $printer->printFile($file);
$modelName = str_replace(' ', '_', ucfirst($name));
$filename = $path . '/' . $modelName . '.php';
file_put_contents($filename, $result);
$helper = $this->getHelper('question');
$tableExists = \DBManager::get()->execute('SHOW TABLES LIKE ?', [$dbTable]);
$describeModel = false;
if ($tableExists) {
$question = new ChoiceQuestion(
"\nDescribe model:\n",
$modelName
);
$describeModel = $helper->ask($input, $output, $question);
}
if ($describeModel) {
$greetInput = new ArrayInput([
'command' => 'sorm:describe',
'name' => 'Fabien',
'--yell' => true,
]);
$returnCode = $this->getApplication()->doRun($greetInput, $output);
}
return $filename;
}
}
<?php
namespace Studip\Cli\Commands\Make;
use Nette\PhpGenerator\Printer;
final class StudipClassPrinter extends Printer
{
/** length of the line after which the line will break */
public int $wrapLength = 120;
/** indentation character, can be replaced with a sequence of spaces */
public string $indentation = ' ';
/** number of blank lines between properties */
public int $linesBetweenProperties = 0;
/** number of blank lines between methods */
public int $linesBetweenMethods = 1;
public string $returnTypeColon = ': ';
}
...@@ -32,6 +32,8 @@ $commands = [ ...@@ -32,6 +32,8 @@ $commands = [
Commands\Cronjobs\CronjobWorker::class, Commands\Cronjobs\CronjobWorker::class,
Commands\DB\Dump::class, Commands\DB\Dump::class,
Commands\DB\MoveMatrikelnummer::class, Commands\DB\MoveMatrikelnummer::class,
Commands\Make\Migration::class,
Commands\Make\Model::class,
Commands\DI\Reset::class, Commands\DI\Reset::class,
Commands\Files\Dump::class, Commands\Files\Dump::class,
Commands\Fix\Biest7789::class, Commands\Fix\Biest7789::class,
......
...@@ -128,7 +128,8 @@ ...@@ -128,7 +128,8 @@
"symfony/polyfill-php84": "1.30.0", "symfony/polyfill-php84": "1.30.0",
"nyholm/psr7": "1.8.1", "nyholm/psr7": "1.8.1",
"nyholm/psr7-server": "1.1.0", "nyholm/psr7-server": "1.1.0",
"league/oauth2-client": "2.7.0" "league/oauth2-client": "2.7.0",
"nette/php-generator": "4.1.5"
}, },
"replace": { "replace": {
"symfony/polyfill-php73": "*", "symfony/polyfill-php73": "*",
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "cad2a823e38968efd43dc8b63fdd8812", "content-hash": "5ec4df54d92509cb7d4ff4f9dc95803b",
"packages": [ "packages": [
{ {
"name": "algo26-matthias/idna-convert", "name": "algo26-matthias/idna-convert",
...@@ -2026,6 +2026,161 @@ ...@@ -2026,6 +2026,161 @@
}, },
"time": "2022-11-28T03:29:06+00:00" "time": "2022-11-28T03:29:06+00:00"
}, },
{
"name": "nette/php-generator",
"version": "v4.1.5",
"source": {
"type": "git",
"url": "https://github.com/nette/php-generator.git",
"reference": "690b00d81d42d5633e4457c43ef9754573b6f9d6"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/nette/php-generator/zipball/690b00d81d42d5633e4457c43ef9754573b6f9d6",
"reference": "690b00d81d42d5633e4457c43ef9754573b6f9d6",
"shasum": ""
},
"require": {
"nette/utils": "^3.2.9 || ^4.0",
"php": "8.0 - 8.3"
},
"require-dev": {
"jetbrains/phpstorm-attributes": "dev-master",
"nette/tester": "^2.4",
"nikic/php-parser": "^4.18 || ^5.0",
"phpstan/phpstan": "^1.0",
"tracy/tracy": "^2.8"
},
"suggest": {
"nikic/php-parser": "to use ClassType::from(withBodies: true) & ClassType::fromCode()"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "4.1-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause",
"GPL-2.0-only",
"GPL-3.0-only"
],
"authors": [
{
"name": "David Grudl",
"homepage": "https://davidgrudl.com"
},
{
"name": "Nette Community",
"homepage": "https://nette.org/contributors"
}
],
"description": "🐘 Nette PHP Generator: generates neat PHP code for you. Supports new PHP 8.3 features.",
"homepage": "https://nette.org",
"keywords": [
"code",
"nette",
"php",
"scaffolding"
],
"support": {
"issues": "https://github.com/nette/php-generator/issues",
"source": "https://github.com/nette/php-generator/tree/v4.1.5"
},
"time": "2024-05-12T17:31:02+00:00"
},
{
"name": "nette/utils",
"version": "v4.0.5",
"source": {
"type": "git",
"url": "https://github.com/nette/utils.git",
"reference": "736c567e257dbe0fcf6ce81b4d6dbe05c6899f96"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/nette/utils/zipball/736c567e257dbe0fcf6ce81b4d6dbe05c6899f96",
"reference": "736c567e257dbe0fcf6ce81b4d6dbe05c6899f96",
"shasum": ""
},
"require": {
"php": "8.0 - 8.4"
},
"conflict": {
"nette/finder": "<3",
"nette/schema": "<1.2.2"
},
"require-dev": {
"jetbrains/phpstorm-attributes": "dev-master",
"nette/tester": "^2.5",
"phpstan/phpstan": "^1.0",
"tracy/tracy": "^2.9"
},
"suggest": {
"ext-gd": "to use Image",
"ext-iconv": "to use Strings::webalize(), toAscii(), chr() and reverse()",
"ext-intl": "to use Strings::webalize(), toAscii(), normalize() and compare()",
"ext-json": "to use Nette\\Utils\\Json",
"ext-mbstring": "to use Strings::lower() etc...",
"ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "4.0-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause",
"GPL-2.0-only",
"GPL-3.0-only"
],
"authors": [
{
"name": "David Grudl",
"homepage": "https://davidgrudl.com"
},
{
"name": "Nette Community",
"homepage": "https://nette.org/contributors"
}
],
"description": "🛠 Nette Utils: lightweight utilities for string & array manipulation, image handling, safe JSON encoding/decoding, validation, slug or strong password generating etc.",
"homepage": "https://nette.org",
"keywords": [
"array",
"core",
"datetime",
"images",
"json",
"nette",
"paginator",
"password",
"slugify",
"string",
"unicode",
"utf-8",
"utility",
"validation"
],
"support": {
"issues": "https://github.com/nette/utils/issues",
"source": "https://github.com/nette/utils/tree/v4.0.5"
},
"time": "2024-08-07T15:39:19+00:00"
},
{ {
"name": "nikic/fast-route", "name": "nikic/fast-route",
"version": "v1.3.0", "version": "v1.3.0",
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment