Skip to content

Commit 6efad06

Browse files
committed
feat: improve make:migration
1 parent b8016e0 commit 6efad06

File tree

5 files changed

+128
-98
lines changed

5 files changed

+128
-98
lines changed

packages/database/src/Commands/MakeMigrationCommand.php

Lines changed: 57 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,14 @@
99
use Tempest\Console\ConsoleCommand;
1010
use Tempest\Core\PublishesFiles;
1111
use Tempest\Database\Enums\MigrationType;
12-
use Tempest\Database\Stubs\MigrationStub;
12+
use Tempest\Database\Stubs\ObjectMigrationStub;
13+
use Tempest\Database\Stubs\UpMigrationStub;
1314
use Tempest\Discovery\SkipDiscovery;
1415
use Tempest\Generation\ClassManipulator;
1516
use Tempest\Generation\DataObjects\StubFile;
1617
use Tempest\Generation\Exceptions\FileGenerationFailedException;
1718
use Tempest\Generation\Exceptions\FileGenerationWasAborted;
19+
use Tempest\Support\Str;
1820
use Tempest\Validation\Rules\EndsWith;
1921
use Tempest\Validation\Rules\IsNotEmptyString;
2022

@@ -30,21 +32,21 @@ final class MakeMigrationCommand
3032
aliases: ['migration:make', 'migration:create', 'create:migration'],
3133
)]
3234
public function __invoke(
33-
#[ConsoleArgument(
34-
description: 'The file name of the migration',
35-
)]
35+
#[ConsoleArgument(description: 'The file name of the migration')]
3636
string $fileName,
37-
#[ConsoleArgument(
38-
name: 'type',
39-
description: 'The type of the migration to create',
40-
)]
37+
#[ConsoleArgument(name: 'type', description: 'The type of the migration to create')]
4138
MigrationType $migrationType = MigrationType::OBJECT,
4239
): void {
4340
try {
44-
$stubFile = $this->getStubFileFromMigrationType($migrationType);
41+
$stub = match ($migrationType) {
42+
MigrationType::RAW => StubFile::from(dirname(__DIR__) . '/Stubs/migration.stub.sql'),
43+
MigrationType::OBJECT => StubFile::from(ObjectMigrationStub::class),
44+
MigrationType::UP => StubFile::from(UpMigrationStub::class),
45+
};
46+
4547
$targetPath = match ($migrationType) {
46-
MigrationType::RAW => $this->generateRawFile($fileName, $stubFile),
47-
default => $this->generateClassFile($fileName, $stubFile),
48+
MigrationType::RAW => $this->generateRawFile($fileName, $stub),
49+
default => $this->generateClassFile($fileName, $stub),
4850
};
4951

5052
$this->success(sprintf('Migration file successfully created at "%s".', $targetPath));
@@ -53,36 +55,36 @@ public function __invoke(
5355
}
5456
}
5557

56-
/**
57-
* Generates a raw migration file.
58-
* @param string $fileName The name of the file.
59-
* @param StubFile $stubFile The stub file to use.
60-
*
61-
* @return string The path to the generated file.
62-
*/
63-
private function generateRawFile(
64-
string $fileName,
65-
StubFile $stubFile,
66-
): string {
67-
$now = date('Y-m-d');
68-
$tableName = str($fileName)->snake()->toString();
69-
$suggestedPath = str($this->getSuggestedPath('Dummy'))
70-
->replace(
71-
['Dummy', '.php'],
72-
[$now . '_' . $tableName, '.sql'],
73-
)
58+
private function generateRawFile(string $filename, StubFile $stubFile): string
59+
{
60+
$tableName = str($filename)
61+
->snake()
62+
->stripStart('create')
63+
->stripEnd('table')
64+
->stripStart('_')
65+
->stripEnd('_')
7466
->toString();
7567

68+
$filename = str($filename)
69+
->start('create_')
70+
->finish('_table')
71+
->toString();
72+
73+
$suggestedPath = Str\replace(
74+
string: $this->getSuggestedPath('Dummy'),
75+
search: ['Dummy', '.php'],
76+
replace: [date('Y-m-d') . '_' . $filename, '.sql'],
77+
);
78+
7679
$targetPath = $this->promptTargetPath($suggestedPath, rules: [
7780
new IsNotEmptyString(),
7881
new EndsWith('.sql'),
7982
]);
80-
$shouldOverride = $this->askForOverride($targetPath);
8183

8284
$this->stubFileGenerator->generateRawFile(
8385
stubFile: $stubFile,
8486
targetPath: $targetPath,
85-
shouldOverride: $shouldOverride,
87+
shouldOverride: $this->askForOverride($targetPath),
8688
replacements: [
8789
'DummyTableName' => $tableName,
8890
],
@@ -91,48 +93,41 @@ private function generateRawFile(
9193
return $targetPath;
9294
}
9395

94-
/**
95-
* Generates a class migration file.
96-
*
97-
* @param string $fileName The name of the file.
98-
* @param StubFile $stubFile The stub file to use.
99-
*
100-
* @return string The path to the generated file.
101-
*/
102-
private function generateClassFile(
103-
string $fileName,
104-
StubFile $stubFile,
105-
): string {
106-
$suggestedPath = $this->getSuggestedPath($fileName);
107-
$targetPath = $this->promptTargetPath($suggestedPath);
108-
$shouldOverride = $this->askForOverride($targetPath);
96+
private function generateClassFile(string $filename, StubFile $stubFile): string
97+
{
98+
$tableName = str($filename)
99+
->snake()
100+
->stripStart('create')
101+
->stripEnd('table')
102+
->stripStart('_')
103+
->stripEnd('_')
104+
->toString();
105+
106+
$filename = str($filename)
107+
->afterLast(['\\', '/'])
108+
->start('Create')
109+
->finish('Table')
110+
->when(
111+
condition: Str\contains($filename, ['\\', '/']),
112+
callback: fn ($path) => $path->prepend(Str\before_last($filename, ['\\', '/']), '/'),
113+
)
114+
->toString();
115+
116+
$targetPath = $this->promptTargetPath($this->getSuggestedPath($filename));
109117

110118
$this->stubFileGenerator->generateClassFile(
111119
stubFile: $stubFile,
112120
targetPath: $targetPath,
113-
shouldOverride: $shouldOverride,
121+
shouldOverride: $this->askForOverride($targetPath),
114122
replacements: [
115123
'dummy-date' => date('Y-m-d'),
116-
'dummy-table-name' => str($fileName)->snake()->toString(),
124+
'dummy-table-name' => $tableName,
117125
],
118126
manipulations: [
119-
fn (ClassManipulator $class) => $class->removeClassAttribute(SkipDiscovery::class),
127+
static fn (ClassManipulator $class) => $class->removeClassAttribute(SkipDiscovery::class),
120128
],
121129
);
122130

123131
return $targetPath;
124132
}
125-
126-
private function getStubFileFromMigrationType(MigrationType $migrationType): StubFile
127-
{
128-
try {
129-
return match ($migrationType) {
130-
MigrationType::RAW => StubFile::from(dirname(__DIR__) . '/Stubs/migration.stub.sql'),
131-
MigrationType::OBJECT => StubFile::from(MigrationStub::class), // @phpstan-ignore match.alwaysTrue (Because this is a guardrail for the future implementations)
132-
default => throw new InvalidArgumentException(sprintf('The "%s" migration type has no supported stub file.', $migrationType->value)),
133-
};
134-
} catch (InvalidArgumentException $invalidArgumentException) {
135-
throw new FileGenerationFailedException(sprintf('Cannot retrieve stub file: %s', $invalidArgumentException->getMessage()));
136-
}
137-
}
138133
}

packages/database/src/Enums/MigrationType.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@
55
namespace Tempest\Database\Enums;
66

77
/**
8-
* Represents the type of migration.
9-
* Used to differentiate between raw and class migrations.
8+
* Used by the `make:migration` command to differentiate the type of migration to be created.
109
*/
1110
enum MigrationType: string
1211
{
13-
case RAW = 'raw'; // A raw migration file ( .sql )
14-
case OBJECT = 'class'; // A classic migration class file
12+
case RAW = 'raw';
13+
case OBJECT = 'class';
14+
case UP = 'up';
1515
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Tempest\Database\Stubs;
6+
7+
use Tempest\Database\MigratesDown;
8+
use Tempest\Database\MigratesUp;
9+
use Tempest\Database\QueryStatement;
10+
use Tempest\Database\QueryStatements\CreateTableStatement;
11+
use Tempest\Database\QueryStatements\DropTableStatement;
12+
use Tempest\Discovery\SkipDiscovery;
13+
14+
#[SkipDiscovery]
15+
final class ObjectMigrationStub implements MigratesUp, MigratesDown
16+
{
17+
public string $name = 'dummy-date_dummy-table-name';
18+
19+
public function up(): QueryStatement
20+
{
21+
return new CreateTableStatement('dummy-table-name')
22+
->primary()
23+
->datetime('created_at')
24+
->datetime('updated_at');
25+
}
26+
27+
public function down(): QueryStatement
28+
{
29+
return new DropTableStatement('dummy-table-name');
30+
}
31+
}

packages/database/src/Stubs/MigrationStub.php renamed to packages/database/src/Stubs/UpMigrationStub.php

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,17 @@
77
use Tempest\Database\MigratesUp;
88
use Tempest\Database\QueryStatement;
99
use Tempest\Database\QueryStatements\CreateTableStatement;
10-
use Tempest\Database\QueryStatements\DropTableStatement;
1110
use Tempest\Discovery\SkipDiscovery;
1211

1312
#[SkipDiscovery]
14-
final class MigrationStub implements MigratesUp
13+
final class UpMigrationStub implements MigratesUp
1514
{
1615
public string $name = 'dummy-date_dummy-table-name';
1716

1817
public function up(): QueryStatement
1918
{
20-
return new CreateTableStatement(
21-
tableName: 'dummy-table-name',
22-
)
19+
return new CreateTableStatement('dummy-table-name')
2320
->primary()
24-
->text('name')
2521
->datetime('created_at')
2622
->datetime('updated_at');
2723
}

tests/Integration/Database/Commands/MakeMigrationCommandTest.php

Lines changed: 34 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@
55
namespace Tests\Tempest\Integration\Database\Commands;
66

77
use PHPUnit\Framework\Attributes\DataProvider;
8+
use PHPUnit\Framework\Attributes\PostCondition;
9+
use PHPUnit\Framework\Attributes\PreCondition;
810
use PHPUnit\Framework\Attributes\Test;
11+
use PHPUnit\Framework\Attributes\TestWith;
912
use Tempest\Support\Namespace\Psr4Namespace;
1013
use Tests\Tempest\Integration\FrameworkIntegrationTestCase;
1114

@@ -14,71 +17,76 @@
1417
*/
1518
final class MakeMigrationCommandTest extends FrameworkIntegrationTestCase
1619
{
17-
protected function setUp(): void
20+
#[PreCondition]
21+
protected function configure(): void
1822
{
19-
parent::setUp();
20-
21-
$this->installer->configure(
22-
__DIR__ . '/install',
23-
new Psr4Namespace('App\\', __DIR__ . '/install/App'),
24-
);
23+
$this->installer->configure(__DIR__ . '/install', new Psr4Namespace('App\\', __DIR__ . '/install/App'));
2524
}
2625

27-
protected function tearDown(): void
26+
#[PostCondition]
27+
protected function cleanup(): void
2828
{
2929
$this->installer->clean();
30-
31-
parent::tearDown();
3230
}
3331

3432
#[Test]
3533
#[DataProvider('command_input_provider')]
36-
public function make_command(
37-
string $commandArgs,
38-
string $expectedPath,
39-
string $expectedNamespace,
40-
): void {
34+
public function make_command(string $commandArgs, string $expectedPath, string $expectedNamespace, string $expectContains): void
35+
{
4136
$this->console
4237
->call("make:migration {$commandArgs}")
4338
->submit();
4439

4540
$this->installer
4641
->assertFileExists($expectedPath)
4742
->assertFileNotContains($expectedPath, 'SkipDiscovery')
48-
->assertFileContains($expectedPath, 'namespace ' . $expectedNamespace . ';');
43+
->assertFileContains($expectedPath, 'namespace ' . $expectedNamespace . ';')
44+
->assertFileContains($expectedPath, $expectContains);
4945
}
5046

5147
public static function command_input_provider(): array
5248
{
5349
return [
5450
'make_with_defaults' => [
55-
'commandArgs' => 'BookMigration',
56-
'expectedPath' => 'App/BookMigration.php',
51+
'commandArgs' => 'Books',
52+
'expectedPath' => 'App/CreateBooksTable.php',
5753
'expectedNamespace' => 'App',
54+
'expectContains' => 'MigratesUp, MigratesDown',
55+
],
56+
'make_up' => [
57+
'commandArgs' => 'CreateBooksTable up',
58+
'expectedPath' => 'App/CreateBooksTable.php',
59+
'expectedNamespace' => 'App',
60+
'expectContains' => 'MigratesUp',
5861
],
5962
'make_with_other_namespace' => [
60-
'commandArgs' => 'Books\\BookMigration',
61-
'expectedPath' => 'App/Books/BookMigration.php',
63+
'commandArgs' => 'Books\\CreateBooksTable',
64+
'expectedPath' => 'App/Books/CreateBooksTable.php',
6265
'expectedNamespace' => 'App\\Books',
66+
'expectContains' => 'MigratesUp, MigratesDown',
6367
],
6468
'make_with_input_path' => [
65-
'commandArgs' => 'Books/BookMigration',
66-
'expectedPath' => 'App/Books/BookMigration.php',
69+
'commandArgs' => 'Books/CreateBooksTable',
70+
'expectedPath' => 'App/Books/CreateBooksTable.php',
6771
'expectedNamespace' => 'App\\Books',
72+
'expectContains' => 'MigratesUp, MigratesDown',
6873
],
6974
];
7075
}
7176

7277
#[Test]
73-
public function raw_migration(): void
78+
#[TestWith(['create_books_table', 'create_books_table'])]
79+
#[TestWith(['books', 'create_books_table'])]
80+
public function raw_migration(string $filename, string $expectedFilename): void
7481
{
7582
$this->console
76-
->call('make:migration book_migration raw')
83+
->call("make:migration {$filename} raw")
7784
->submit();
7885

79-
$filePath = sprintf('App/%s_book_migration.sql', date('Y-m-d'));
86+
$filePath = sprintf('App/%s_%s.sql', date('Y-m-d'), $expectedFilename);
87+
8088
$this->installer
8189
->assertFileExists($filePath)
82-
->assertFileContains($filePath, 'CREATE TABLE book_migration');
90+
->assertFileContains($filePath, 'CREATE TABLE books');
8391
}
8492
}

0 commit comments

Comments
 (0)