Skip to content

Commit de2334b

Browse files
authored
feat(database): dissociate down migrations from up migrations (#1513)
1 parent b7dc71e commit de2334b

File tree

52 files changed

+385
-715
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+385
-715
lines changed

packages/auth/src/Install/CreatePermissionsTable.php

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,14 @@
44

55
namespace Tempest\Auth\Install;
66

7-
use Tempest\Database\DatabaseMigration;
7+
use Tempest\Database\MigratesDown;
8+
use Tempest\Database\MigratesUp;
89
use Tempest\Database\QueryStatements\CreateTableStatement;
910
use Tempest\Database\QueryStatements\DropTableStatement;
1011
use Tempest\Discovery\SkipDiscovery;
1112

1213
#[SkipDiscovery]
13-
final class CreatePermissionsTable implements DatabaseMigration
14+
final class CreatePermissionsTable implements MigratesUp, MigratesDown
1415
{
1516
private(set) string $name = '0000-00-01_create_permissions_table';
1617

@@ -23,6 +24,6 @@ public function up(): CreateTableStatement
2324

2425
public function down(): DropTableStatement
2526
{
26-
return DropTableStatement::forModel(Permission::class);
27+
return new DropTableStatement('permissions');
2728
}
2829
}

packages/auth/src/Install/CreateUserPermissionsTable.php

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,14 @@
44

55
namespace Tempest\Auth\Install;
66

7-
use Tempest\Database\DatabaseMigration;
7+
use Tempest\Database\MigratesDown;
8+
use Tempest\Database\MigratesUp;
89
use Tempest\Database\QueryStatements\CreateTableStatement;
910
use Tempest\Database\QueryStatements\DropTableStatement;
1011
use Tempest\Discovery\SkipDiscovery;
1112

1213
#[SkipDiscovery]
13-
final class CreateUserPermissionsTable implements DatabaseMigration
14+
final class CreateUserPermissionsTable implements MigratesUp, MigratesDown
1415
{
1516
private(set) string $name = '0000-00-02_create_user_permissions_table';
1617

@@ -24,6 +25,6 @@ public function up(): CreateTableStatement
2425

2526
public function down(): DropTableStatement
2627
{
27-
return DropTableStatement::forModel(Permission::class);
28+
return new DropTableStatement('user_permissions');
2829
}
2930
}

packages/auth/src/Install/CreateUsersTable.php

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,14 @@
44

55
namespace Tempest\Auth\Install;
66

7-
use Tempest\Database\DatabaseMigration;
7+
use Tempest\Database\MigratesDown;
8+
use Tempest\Database\MigratesUp;
89
use Tempest\Database\QueryStatements\CreateTableStatement;
910
use Tempest\Database\QueryStatements\DropTableStatement;
1011
use Tempest\Discovery\SkipDiscovery;
1112

1213
#[SkipDiscovery]
13-
final class CreateUsersTable implements DatabaseMigration
14+
final class CreateUsersTable implements MigratesUp, MigratesDown
1415
{
1516
private(set) string $name = '0000-00-00_create_users_table';
1617

@@ -26,6 +27,6 @@ public function up(): CreateTableStatement
2627

2728
public function down(): DropTableStatement
2829
{
29-
return DropTableStatement::forModel(User::class);
30+
return new DropTableStatement('users');
3031
}
3132
}

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/DatabaseMigration.php

Lines changed: 0 additions & 16 deletions
This file was deleted.

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
}

packages/database/src/GenericDatabaseMigration.php

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
use Tempest\Database\QueryStatements\RawStatement;
88

9-
final class GenericDatabaseMigration implements DatabaseMigration
9+
final class GenericDatabaseMigration implements MigratesUp
1010
{
1111
public string $name;
1212

@@ -21,9 +21,4 @@ public function up(): QueryStatement
2121
{
2222
return new RawStatement($this->content);
2323
}
24-
25-
public function down(): ?QueryStatement
26-
{
27-
return null;
28-
}
2924
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Tempest\Database;
6+
7+
interface MigratesDown
8+
{
9+
public string $name {
10+
get;
11+
}
12+
13+
public function down(): QueryStatement;
14+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Tempest\Database;
6+
7+
interface MigratesUp
8+
{
9+
public string $name {
10+
get;
11+
}
12+
13+
public function up(): QueryStatement;
14+
}

packages/database/src/MigrationDiscovery.php

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ public function __construct(
2323

2424
public function discover(DiscoveryLocation $location, ClassReflector $class): void
2525
{
26-
if (! $class->implements(DatabaseMigration::class)) {
26+
if (! $class->implements(MigratesUp::class) && ! $class->implements(MigratesDown::class)) {
2727
return;
2828
}
2929

@@ -64,19 +64,17 @@ public function discoverPath(DiscoveryLocation $location, string $path): void
6464

6565
public function apply(): void
6666
{
67-
/** @var DatabaseMigration[] $resolved */
67+
/** @var array<MigratesUp|MigratesDown> $resolved */
6868
$resolved = [];
69+
6970
foreach ($this->discoveryItems as $discoveryItem) {
7071
if (is_string($discoveryItem)) {
7172
$resolved[] = $this->container->get($discoveryItem);
72-
} elseif ($discoveryItem instanceof DatabaseMigration) {
73+
} elseif ($discoveryItem instanceof MigratesUp || $discoveryItem instanceof MigratesDown) {
7374
$resolved[] = $discoveryItem;
7475
}
7576
}
7677

77-
$this->container->singleton(
78-
RunnableMigrations::class,
79-
new RunnableMigrations($resolved),
80-
);
78+
$this->container->singleton(RunnableMigrations::class, new RunnableMigrations($resolved));
8179
}
8280
}

0 commit comments

Comments
 (0)