Skip to content

Commit 0a49e1f

Browse files
authored
feat(database): add database seeder support (#1354)
1 parent 9d8449c commit 0a49e1f

File tree

11 files changed

+401
-3
lines changed

11 files changed

+401
-3
lines changed
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
namespace Tempest\Database\Config;
4+
5+
final class SeederConfig
6+
{
7+
public function __construct(
8+
/** @var array<array-key, class-string<\Tempest\Database\DatabaseSeeder>> */
9+
public array $seeders = [],
10+
) {}
11+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<?php
2+
3+
use Tempest\Database\Config\SeederConfig;
4+
5+
return new SeederConfig();
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
namespace Tempest\Database;
4+
5+
use UnitEnum;
6+
7+
interface DatabaseSeeder
8+
{
9+
public function run(null|string|UnitEnum $database): void;
10+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
namespace Tempest\Database;
4+
5+
use Tempest\Database\Config\SeederConfig;
6+
use Tempest\Discovery\Discovery;
7+
use Tempest\Discovery\DiscoveryLocation;
8+
use Tempest\Discovery\IsDiscovery;
9+
use Tempest\Reflection\ClassReflector;
10+
11+
final class SeederDiscovery implements Discovery
12+
{
13+
use IsDiscovery;
14+
15+
public function __construct(
16+
private SeederConfig $seederConfig,
17+
) {}
18+
19+
public function discover(DiscoveryLocation $location, ClassReflector $class): void
20+
{
21+
if ($class->implements(DatabaseSeeder::class)) {
22+
$this->discoveryItems->add($location, $class->getName());
23+
}
24+
}
25+
26+
public function apply(): void
27+
{
28+
foreach ($this->discoveryItems as $discoveryItem) {
29+
$this->seederConfig->seeders[] = $discoveryItem;
30+
}
31+
}
32+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
<?php
2+
3+
namespace Tempest\Framework\Commands;
4+
5+
use Tempest\Console\ConsoleArgument;
6+
use Tempest\Console\ConsoleCommand;
7+
use Tempest\Console\HasConsole;
8+
use Tempest\Console\Middleware\CautionMiddleware;
9+
use Tempest\Console\Middleware\ForceMiddleware;
10+
use Tempest\Container\Container;
11+
use Tempest\Database\Config\SeederConfig;
12+
13+
final class DatabaseSeedCommand
14+
{
15+
use HasConsole;
16+
17+
public function __construct(
18+
private readonly Container $container,
19+
private readonly SeederConfig $seederConfig,
20+
) {}
21+
22+
#[ConsoleCommand(
23+
aliases: ['db:seed'],
24+
middleware: [ForceMiddleware::class, CautionMiddleware::class],
25+
)]
26+
public function __invoke(
27+
#[ConsoleArgument(description: 'Use a specific database.')]
28+
?string $database = null,
29+
#[ConsoleArgument(description: 'Run all database seeders')]
30+
bool $all = false,
31+
#[ConsoleArgument(description: 'Select one specific seeder to run')]
32+
?string $seeder = null,
33+
): void {
34+
if ($seeder !== null) {
35+
$this->runSeeder($seeder, $database);
36+
return;
37+
}
38+
39+
if (count($this->seederConfig->seeders) === 1) {
40+
$this->runSeeder($this->seederConfig->seeders[0], $database);
41+
return;
42+
}
43+
44+
if ($all) {
45+
$seedersToRun = $this->seederConfig->seeders;
46+
} else {
47+
$seedersToRun = $this->ask(
48+
question: 'Which seeders do you want to run?',
49+
options: $this->seederConfig->seeders,
50+
multiple: true,
51+
);
52+
}
53+
54+
foreach ($seedersToRun as $seederClass) {
55+
$this->runSeeder($seederClass, $database);
56+
}
57+
}
58+
59+
private function runSeeder(string $seederClass, ?string $database): void
60+
{
61+
/** @var \Tempest\Database\DatabaseSeeder $seeder */
62+
$seeder = $this->container->get($seederClass);
63+
$seeder->run($database);
64+
65+
$this->console->keyValue(
66+
key: "<style='fg-gray'>{$seederClass}</style>",
67+
value: "<style='fg-green'>SEEDED</style>",
68+
);
69+
}
70+
}

src/Tempest/Framework/Commands/MigrateFreshCommand.php

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,12 @@ public function __invoke(
3636
bool $validate = true,
3737
#[ConsoleArgument(description: 'Use a specific database.')]
3838
?string $database = null,
39+
#[ConsoleArgument(description: 'Run database seeders after the database has been migrated')]
40+
bool $seed = false,
41+
#[ConsoleArgument(description: 'Run all database seeders after the database has been migrated')]
42+
bool $all = false,
43+
#[ConsoleArgument(description: 'Select one specific seeder to run')]
44+
?string $seeder = null,
3945
): ExitCode {
4046
if ($validate) {
4147
$validationSuccess = $this->console->call(MigrateValidateCommand::class);
@@ -52,7 +58,24 @@ public function __invoke(
5258
$this->console->info('There is no migration to drop.');
5359
}
5460

55-
return $this->console->call(MigrateUpCommand::class, ['fresh' => false, 'validate' => false, 'database' => $database]);
61+
$this->console->call(MigrateUpCommand::class, [
62+
'fresh' => false,
63+
'validate' => false,
64+
'database' => $database,
65+
]);
66+
67+
$seed = $seed || $seeder !== null;
68+
69+
if ($seed) {
70+
$this->console->header('Seeding database');
71+
$this->console->call(DatabaseSeedCommand::class, [
72+
'database' => $database,
73+
'all' => $all,
74+
'seeder' => $seeder,
75+
]);
76+
}
77+
78+
return ExitCode::SUCCESS;
5679
}
5780

5881
#[EventHandler]

src/Tempest/Framework/Commands/MigrateUpCommand.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
use Tempest\Console\ConsoleArgument;
99
use Tempest\Console\ConsoleCommand;
1010
use Tempest\Console\ExitCode;
11-
use Tempest\Console\Input\ConsoleArgumentBag;
1211
use Tempest\Console\Middleware\CautionMiddleware;
1312
use Tempest\Console\Middleware\ForceMiddleware;
1413
use Tempest\Container\Singleton;
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
namespace Tests\Tempest\Fixtures;
4+
5+
use Tempest\Database\DatabaseSeeder;
6+
use Tests\Tempest\Fixtures\Modules\Books\Models\Book;
7+
use UnitEnum;
8+
9+
use function Tempest\Database\query;
10+
11+
final class SecondTestDatabaseSeeder implements DatabaseSeeder
12+
{
13+
public function run(null|string|UnitEnum $database): void
14+
{
15+
query(Book::class)
16+
->insert(
17+
title: 'Timeline Taxi 2',
18+
)
19+
->onDatabase($database)
20+
->execute();
21+
}
22+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php
2+
3+
namespace Tests\Tempest\Fixtures;
4+
5+
use Tempest\Database\DatabaseSeeder;
6+
use Tempest\Discovery\SkipDiscovery;
7+
use Tests\Tempest\Fixtures\Modules\Books\Models\Book;
8+
use UnitEnum;
9+
10+
use function Tempest\Database\query;
11+
12+
final class TestDatabaseSeeder implements DatabaseSeeder
13+
{
14+
public function run(null|string|UnitEnum $database): void
15+
{
16+
query(Book::class)
17+
->insert(
18+
title: 'Timeline Taxi',
19+
)
20+
->onDatabase($database)
21+
->execute();
22+
}
23+
}

tests/Integration/Database/MultiDatabaseTest.php

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?php
22

3-
namespace Integration\Database;
3+
namespace Tests\Tempest\Integration\Database;
44

55
use PDOException;
66
use Tempest\Container\Exceptions\TaggedDependencyCouldNotBeResolved;
@@ -13,7 +13,9 @@
1313
use Tempest\Database\Migrations\CreateMigrationsTable;
1414
use Tempest\Database\Migrations\Migration;
1515
use Tempest\Database\Migrations\MigrationManager;
16+
use Tests\Tempest\Fixtures\Migrations\CreateBookTable;
1617
use Tests\Tempest\Fixtures\Migrations\CreatePublishersTable;
18+
use Tests\Tempest\Fixtures\Modules\Books\Models\Book;
1719
use Tests\Tempest\Fixtures\Modules\Books\Models\Publisher;
1820
use Tests\Tempest\Integration\Database\Fixtures\MigrationForBackup;
1921
use Tests\Tempest\Integration\Database\Fixtures\MigrationForMain;
@@ -271,6 +273,68 @@ public function test_should_migrate(): void
271273
$this->assertTableDoesNotExist('main_table', 'backup');
272274
}
273275

276+
public function test_database_seed_on_selected_database(): void
277+
{
278+
/** @var MigrationManager $migrationManager */
279+
$migrationManager = $this->container->get(MigrationManager::class);
280+
281+
$migrationManager->onDatabase('main')->executeUp(new CreateMigrationsTable());
282+
$migrationManager->onDatabase('main')->executeUp(new CreateBookTable());
283+
$migrationManager->onDatabase('backup')->executeUp(new CreateMigrationsTable());
284+
$migrationManager->onDatabase('backup')->executeUp(new CreateBookTable());
285+
286+
$this->console
287+
->call('db:seed --database=main --all')
288+
->assertSuccess();
289+
290+
$this->assertSame(
291+
'Timeline Taxi',
292+
query(Book::class)->select()->onDatabase('main')->first()->title,
293+
);
294+
295+
$this->assertNull(
296+
query(Book::class)->select()->onDatabase('backup')->first(),
297+
);
298+
299+
$this->console
300+
->call('db:seed --database=backup --all')
301+
->assertSuccess();
302+
303+
/** @var Book $book */
304+
/** @phpstan-ignore-next-line */
305+
$book = query(Book::class)->select()->onDatabase('backup')->first();
306+
307+
$this->assertSame(
308+
'Timeline Taxi',
309+
$book->title,
310+
);
311+
}
312+
313+
public function test_migrate_fresh_seed_on_selected_database(): void
314+
{
315+
$this->console
316+
->call('migrate:fresh --seed --database=main --all')
317+
->assertSuccess();
318+
319+
$this->assertSame(
320+
'Timeline Taxi',
321+
query(Book::class)->select()->onDatabase('main')->first()->title,
322+
);
323+
324+
$this->assertException(PDOException::class, function (): void {
325+
query(Book::class)->select()->onDatabase('backup')->first();
326+
});
327+
328+
$this->console
329+
->call('migrate:fresh --seed --database=backup --all')
330+
->assertSuccess();
331+
332+
$this->assertSame(
333+
'Timeline Taxi',
334+
query(Book::class)->select()->onDatabase('backup')->first()->title,
335+
);
336+
}
337+
274338
private function assertTableExists(string $tableName, string $onDatabase): void
275339
{
276340
$this->assertTrue(query($tableName)->count()->onDatabase($onDatabase)->execute() >= 0);

0 commit comments

Comments
 (0)