Skip to content

Commit e34654a

Browse files
gturpin-devbrendt
andauthored
feat(console): add make:migration command (#871)
Co-authored-by: brendt <[email protected]>
1 parent 2f0b247 commit e34654a

File tree

7 files changed

+278
-2
lines changed

7 files changed

+278
-2
lines changed

src/Tempest/Core/src/PublishesFiles.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -164,16 +164,18 @@ public function getSuggestedPath(string $className, ?string $pathPrefix = null,
164164
/**
165165
* Prompt the user for the target path to save the generated file.
166166
* @param string $suggestedPath The suggested path to show to the user.
167+
* @param \Tempest\Validation\Rule[]|null $rules Rules to use instead of the default ones.
168+
*
167169
* @return string The target path that the user has chosen.
168170
*/
169-
public function promptTargetPath(string $suggestedPath): string
171+
public function promptTargetPath(string $suggestedPath, ?array $rules = null): string
170172
{
171173
$className = to_base_class_name($suggestedPath);
172174

173175
$targetPath = $this->console->ask(
174176
question: sprintf('Where do you want to save the file <em>%s</em>?', $className),
175177
default: to_relative_path(root_path(), $suggestedPath),
176-
validation: [new NotEmpty(), new EndsWith('.php')],
178+
validation: $rules ?? [new NotEmpty(), new EndsWith('.php')],
177179
);
178180

179181
return to_absolute_path(root_path(), $targetPath);
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Tempest\Database\Commands;
6+
7+
use InvalidArgumentException;
8+
use Tempest\Console\ConsoleArgument;
9+
use Tempest\Console\ConsoleCommand;
10+
use Tempest\Core\PublishesFiles;
11+
use Tempest\Database\Enums\MigrationType;
12+
use Tempest\Database\Stubs\MigrationStub;
13+
use Tempest\Generation\DataObjects\StubFile;
14+
use Tempest\Generation\Exceptions\FileGenerationAbortedException;
15+
use Tempest\Generation\Exceptions\FileGenerationFailedException;
16+
use Tempest\Validation\Rules\EndsWith;
17+
use Tempest\Validation\Rules\NotEmpty;
18+
19+
use function Tempest\Support\str;
20+
21+
final class MakeMigrationCommand
22+
{
23+
use PublishesFiles;
24+
25+
#[ConsoleCommand(
26+
name: 'make:migration',
27+
description: 'Creates a new migration file',
28+
aliases: ['migration:make', 'migration:create', 'create:migration'],
29+
)]
30+
public function __invoke(
31+
#[ConsoleArgument(
32+
description: 'The file name of the migration',
33+
)]
34+
string $fileName,
35+
#[ConsoleArgument(
36+
name: 'type',
37+
description: 'The type of the migration to create',
38+
)]
39+
MigrationType $migrationType = MigrationType::OBJECT,
40+
): void {
41+
try {
42+
$stubFile = $this->getStubFileFromMigrationType($migrationType);
43+
$targetPath = match ($migrationType) {
44+
MigrationType::RAW => $this->generateRawFile($fileName, $stubFile),
45+
default => $this->generateClassFile($fileName, $stubFile),
46+
};
47+
48+
$this->success(sprintf('Migration file successfully created at "%s".', $targetPath));
49+
} catch (FileGenerationAbortedException|FileGenerationFailedException|InvalidArgumentException $e) {
50+
$this->error($e->getMessage());
51+
}
52+
}
53+
54+
/**
55+
* Generates a raw migration file.
56+
* @param string $fileName The name of the file.
57+
* @param StubFile $stubFile The stub file to use.
58+
*
59+
* @return string The path to the generated file.
60+
*/
61+
private function generateRawFile(
62+
string $fileName,
63+
StubFile $stubFile,
64+
): string {
65+
$now = date('Y-m-d');
66+
$tableName = str($fileName)->snake()->toString();
67+
$suggestedPath = str($this->getSuggestedPath('Dummy'))
68+
->replace(
69+
['Dummy', '.php'],
70+
[$now . '_' . $tableName, '.sql'],
71+
)
72+
->toString();
73+
74+
$targetPath = $this->promptTargetPath($suggestedPath, rules: [
75+
new NotEmpty(),
76+
new EndsWith('.sql'),
77+
]);
78+
$shouldOverride = $this->askForOverride($targetPath);
79+
80+
$this->stubFileGenerator->generateRawFile(
81+
stubFile: $stubFile,
82+
targetPath: $targetPath,
83+
shouldOverride: $shouldOverride,
84+
replacements: [
85+
'DummyTableName' => $tableName,
86+
],
87+
);
88+
89+
return $targetPath;
90+
}
91+
92+
/**
93+
* Generates a class migration file.
94+
*
95+
* @param string $fileName The name of the file.
96+
* @param StubFile $stubFile The stub file to use.
97+
*
98+
* @return string The path to the generated file.
99+
*/
100+
private function generateClassFile(
101+
string $fileName,
102+
StubFile $stubFile,
103+
): string {
104+
$suggestedPath = $this->getSuggestedPath($fileName);
105+
$targetPath = $this->promptTargetPath($suggestedPath);
106+
$shouldOverride = $this->askForOverride($targetPath);
107+
108+
$this->stubFileGenerator->generateClassFile(
109+
stubFile: $stubFile,
110+
targetPath: $targetPath,
111+
shouldOverride: $shouldOverride,
112+
replacements: [
113+
'dummy-date' => date('Y-m-d'),
114+
'dummy-table-name' => str($fileName)->snake()->toString(),
115+
],
116+
);
117+
118+
return $targetPath;
119+
}
120+
121+
private function getStubFileFromMigrationType(MigrationType $migrationType): StubFile
122+
{
123+
try {
124+
return match ($migrationType) {
125+
MigrationType::RAW => StubFile::from(dirname(__DIR__) . '/Stubs/migration.stub.sql'),
126+
MigrationType::OBJECT => StubFile::from(MigrationStub::class), // @phpstan-ignore match.alwaysTrue (Because this is a guardrail for the future implementations)
127+
default => throw new InvalidArgumentException(sprintf('The "%s" migration type has no supported stub file.', $migrationType->value)),
128+
};
129+
} catch (InvalidArgumentException $invalidArgumentException) {
130+
throw new FileGenerationFailedException(sprintf('Cannot retrieve stub file: %s', $invalidArgumentException->getMessage()));
131+
}
132+
}
133+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Tempest\Database\Enums;
6+
7+
/**
8+
* Represents the type of migration.
9+
* Used to differentiate between raw and class migrations.
10+
*/
11+
enum MigrationType: string
12+
{
13+
case RAW = 'raw'; // A raw migration file ( .sql )
14+
case OBJECT = 'class'; // A classic migration class file
15+
}

src/Tempest/Database/src/MigrationDiscovery.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ public function discoverPath(DiscoveryLocation $location, string $path): void
3939
return;
4040
}
4141

42+
if (str_ends_with($path, '.stub.sql')) {
43+
return;
44+
}
45+
4246
$fileName = pathinfo($path, PATHINFO_FILENAME);
4347

4448
$contents = explode(';', file_get_contents($path));
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Tempest\Database\Stubs;
6+
7+
use Tempest\Database\DatabaseMigration;
8+
use Tempest\Database\Migrations\Migration;
9+
use Tempest\Database\QueryStatement;
10+
use Tempest\Database\QueryStatements\CreateTableStatement;
11+
use Tempest\Database\QueryStatements\DropTableStatement;
12+
13+
final class MigrationStub implements DatabaseMigration
14+
{
15+
public string $name = 'dummy-date_dummy-table-name';
16+
17+
public function up(): QueryStatement
18+
{
19+
return new CreateTableStatement(
20+
tableName: 'dummy-table-name',
21+
)
22+
->primary()
23+
->text('name')
24+
->datetime('created_at')
25+
->datetime('updated_at');
26+
}
27+
28+
public function down(): QueryStatement
29+
{
30+
return new DropTableStatement(
31+
tableName: 'dummy-table-name',
32+
);
33+
}
34+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
CREATE TABLE DummyTableName
2+
(
3+
`id` INTEGER PRIMARY KEY AUTOINCREMENT,
4+
`name` TEXT NOT NULL
5+
);
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Tests\Tempest\Integration\Database\Commands;
6+
7+
use PHPUnit\Framework\Attributes\DataProvider;
8+
use PHPUnit\Framework\Attributes\Test;
9+
use Tempest\Support\Namespace\Psr4Namespace;
10+
use Tests\Tempest\Integration\FrameworkIntegrationTestCase;
11+
12+
/**
13+
* @internal
14+
*/
15+
final class MakeMigrationCommandTest extends FrameworkIntegrationTestCase
16+
{
17+
protected function setUp(): void
18+
{
19+
parent::setUp();
20+
21+
$this->installer->configure(
22+
__DIR__ . '/install',
23+
new Psr4Namespace('App\\', __DIR__ . '/install/App'),
24+
);
25+
}
26+
27+
protected function tearDown(): void
28+
{
29+
$this->installer->clean();
30+
31+
parent::tearDown();
32+
}
33+
34+
#[Test]
35+
#[DataProvider('command_input_provider')]
36+
public function make_command(
37+
string $commandArgs,
38+
string $expectedPath,
39+
string $expectedNamespace,
40+
): void {
41+
$this->console
42+
->call("make:migration {$commandArgs}")
43+
->submit();
44+
45+
$this->installer
46+
->assertFileExists($expectedPath)
47+
->assertFileContains($expectedPath, 'namespace ' . $expectedNamespace . ';');
48+
}
49+
50+
public static function command_input_provider(): array
51+
{
52+
return [
53+
'make_with_defaults' => [
54+
'commandArgs' => 'BookMigration',
55+
'expectedPath' => 'App/BookMigration.php',
56+
'expectedNamespace' => 'App',
57+
],
58+
'make_with_other_namespace' => [
59+
'commandArgs' => 'Books\\BookMigration',
60+
'expectedPath' => 'App/Books/BookMigration.php',
61+
'expectedNamespace' => 'App\\Books',
62+
],
63+
'make_with_input_path' => [
64+
'commandArgs' => 'Books/BookMigration',
65+
'expectedPath' => 'App/Books/BookMigration.php',
66+
'expectedNamespace' => 'App\\Books',
67+
],
68+
];
69+
}
70+
71+
#[Test]
72+
public function raw_migration(): void
73+
{
74+
$this->console
75+
->call('make:migration book_migration raw')
76+
->submit();
77+
78+
$filePath = sprintf('App/%s_book_migration.sql', date('Y-m-d'));
79+
$this->installer
80+
->assertFileExists($filePath)
81+
->assertFileContains($filePath, 'CREATE TABLE book_migration');
82+
}
83+
}

0 commit comments

Comments
 (0)