diff --git a/packages/database/src/Config/SeederConfig.php b/packages/database/src/Config/SeederConfig.php
new file mode 100644
index 000000000..5b64c09de
--- /dev/null
+++ b/packages/database/src/Config/SeederConfig.php
@@ -0,0 +1,11 @@
+> */
+ public array $seeders = [],
+ ) {}
+}
diff --git a/packages/database/src/Config/seeder.config.php b/packages/database/src/Config/seeder.config.php
new file mode 100644
index 000000000..eb487b4d0
--- /dev/null
+++ b/packages/database/src/Config/seeder.config.php
@@ -0,0 +1,5 @@
+implements(DatabaseSeeder::class)) {
+ $this->discoveryItems->add($location, $class->getName());
+ }
+ }
+
+ public function apply(): void
+ {
+ foreach ($this->discoveryItems as $discoveryItem) {
+ $this->seederConfig->seeders[] = $discoveryItem;
+ }
+ }
+}
diff --git a/src/Tempest/Framework/Commands/DatabaseSeedCommand.php b/src/Tempest/Framework/Commands/DatabaseSeedCommand.php
new file mode 100644
index 000000000..033a83492
--- /dev/null
+++ b/src/Tempest/Framework/Commands/DatabaseSeedCommand.php
@@ -0,0 +1,70 @@
+runSeeder($seeder, $database);
+ return;
+ }
+
+ if (count($this->seederConfig->seeders) === 1) {
+ $this->runSeeder($this->seederConfig->seeders[0], $database);
+ return;
+ }
+
+ if ($all) {
+ $seedersToRun = $this->seederConfig->seeders;
+ } else {
+ $seedersToRun = $this->ask(
+ question: 'Which seeders do you want to run?',
+ options: $this->seederConfig->seeders,
+ multiple: true,
+ );
+ }
+
+ foreach ($seedersToRun as $seederClass) {
+ $this->runSeeder($seederClass, $database);
+ }
+ }
+
+ private function runSeeder(string $seederClass, ?string $database): void
+ {
+ /** @var \Tempest\Database\DatabaseSeeder $seeder */
+ $seeder = $this->container->get($seederClass);
+ $seeder->run($database);
+
+ $this->console->keyValue(
+ key: "",
+ value: "",
+ );
+ }
+}
diff --git a/src/Tempest/Framework/Commands/MigrateFreshCommand.php b/src/Tempest/Framework/Commands/MigrateFreshCommand.php
index 9d7c32021..66eb87df4 100644
--- a/src/Tempest/Framework/Commands/MigrateFreshCommand.php
+++ b/src/Tempest/Framework/Commands/MigrateFreshCommand.php
@@ -36,6 +36,12 @@ public function __invoke(
bool $validate = true,
#[ConsoleArgument(description: 'Use a specific database.')]
?string $database = null,
+ #[ConsoleArgument(description: 'Run database seeders after the database has been migrated')]
+ bool $seed = false,
+ #[ConsoleArgument(description: 'Run all database seeders after the database has been migrated')]
+ bool $all = false,
+ #[ConsoleArgument(description: 'Select one specific seeder to run')]
+ ?string $seeder = null,
): ExitCode {
if ($validate) {
$validationSuccess = $this->console->call(MigrateValidateCommand::class);
@@ -52,7 +58,24 @@ public function __invoke(
$this->console->info('There is no migration to drop.');
}
- return $this->console->call(MigrateUpCommand::class, ['fresh' => false, 'validate' => false, 'database' => $database]);
+ $this->console->call(MigrateUpCommand::class, [
+ 'fresh' => false,
+ 'validate' => false,
+ 'database' => $database,
+ ]);
+
+ $seed = $seed || $seeder !== null;
+
+ if ($seed) {
+ $this->console->header('Seeding database');
+ $this->console->call(DatabaseSeedCommand::class, [
+ 'database' => $database,
+ 'all' => $all,
+ 'seeder' => $seeder,
+ ]);
+ }
+
+ return ExitCode::SUCCESS;
}
#[EventHandler]
diff --git a/src/Tempest/Framework/Commands/MigrateUpCommand.php b/src/Tempest/Framework/Commands/MigrateUpCommand.php
index 68ad321cd..2a3806204 100644
--- a/src/Tempest/Framework/Commands/MigrateUpCommand.php
+++ b/src/Tempest/Framework/Commands/MigrateUpCommand.php
@@ -8,7 +8,6 @@
use Tempest\Console\ConsoleArgument;
use Tempest\Console\ConsoleCommand;
use Tempest\Console\ExitCode;
-use Tempest\Console\Input\ConsoleArgumentBag;
use Tempest\Console\Middleware\CautionMiddleware;
use Tempest\Console\Middleware\ForceMiddleware;
use Tempest\Container\Singleton;
diff --git a/tests/Fixtures/SecondTestDatabaseSeeder.php b/tests/Fixtures/SecondTestDatabaseSeeder.php
new file mode 100644
index 000000000..6015a40ed
--- /dev/null
+++ b/tests/Fixtures/SecondTestDatabaseSeeder.php
@@ -0,0 +1,22 @@
+insert(
+ title: 'Timeline Taxi 2',
+ )
+ ->onDatabase($database)
+ ->execute();
+ }
+}
diff --git a/tests/Fixtures/TestDatabaseSeeder.php b/tests/Fixtures/TestDatabaseSeeder.php
new file mode 100644
index 000000000..b00a6af65
--- /dev/null
+++ b/tests/Fixtures/TestDatabaseSeeder.php
@@ -0,0 +1,23 @@
+insert(
+ title: 'Timeline Taxi',
+ )
+ ->onDatabase($database)
+ ->execute();
+ }
+}
diff --git a/tests/Integration/Database/MultiDatabaseTest.php b/tests/Integration/Database/MultiDatabaseTest.php
index b187093f2..ea0e223a5 100644
--- a/tests/Integration/Database/MultiDatabaseTest.php
+++ b/tests/Integration/Database/MultiDatabaseTest.php
@@ -1,6 +1,6 @@
assertTableDoesNotExist('main_table', 'backup');
}
+ public function test_database_seed_on_selected_database(): void
+ {
+ /** @var MigrationManager $migrationManager */
+ $migrationManager = $this->container->get(MigrationManager::class);
+
+ $migrationManager->onDatabase('main')->executeUp(new CreateMigrationsTable());
+ $migrationManager->onDatabase('main')->executeUp(new CreateBookTable());
+ $migrationManager->onDatabase('backup')->executeUp(new CreateMigrationsTable());
+ $migrationManager->onDatabase('backup')->executeUp(new CreateBookTable());
+
+ $this->console
+ ->call('db:seed --database=main --all')
+ ->assertSuccess();
+
+ $this->assertSame(
+ 'Timeline Taxi',
+ query(Book::class)->select()->onDatabase('main')->first()->title,
+ );
+
+ $this->assertNull(
+ query(Book::class)->select()->onDatabase('backup')->first(),
+ );
+
+ $this->console
+ ->call('db:seed --database=backup --all')
+ ->assertSuccess();
+
+ /** @var Book $book */
+ /** @phpstan-ignore-next-line */
+ $book = query(Book::class)->select()->onDatabase('backup')->first();
+
+ $this->assertSame(
+ 'Timeline Taxi',
+ $book->title,
+ );
+ }
+
+ public function test_migrate_fresh_seed_on_selected_database(): void
+ {
+ $this->console
+ ->call('migrate:fresh --seed --database=main --all')
+ ->assertSuccess();
+
+ $this->assertSame(
+ 'Timeline Taxi',
+ query(Book::class)->select()->onDatabase('main')->first()->title,
+ );
+
+ $this->assertException(PDOException::class, function (): void {
+ query(Book::class)->select()->onDatabase('backup')->first();
+ });
+
+ $this->console
+ ->call('migrate:fresh --seed --database=backup --all')
+ ->assertSuccess();
+
+ $this->assertSame(
+ 'Timeline Taxi',
+ query(Book::class)->select()->onDatabase('backup')->first()->title,
+ );
+ }
+
private function assertTableExists(string $tableName, string $onDatabase): void
{
$this->assertTrue(query($tableName)->count()->onDatabase($onDatabase)->execute() >= 0);
diff --git a/tests/Integration/Framework/Commands/DatabaseSeedCommandTest.php b/tests/Integration/Framework/Commands/DatabaseSeedCommandTest.php
new file mode 100644
index 000000000..54630403b
--- /dev/null
+++ b/tests/Integration/Framework/Commands/DatabaseSeedCommandTest.php
@@ -0,0 +1,139 @@
+migrate(
+ CreateMigrationsTable::class,
+ CreatePublishersTable::class,
+ CreateAuthorTable::class,
+ CreateBookTable::class,
+ );
+
+ $this->console
+ ->call('db:seed')
+ ->assertSee(TestDatabaseSeeder::class)
+ ->assertSee(SecondTestDatabaseSeeder::class)
+ ->submit('1')
+ ->submit()
+ ->assertSuccess();
+
+ $this->assertSame(1, query(Book::class)->count()->execute());
+ }
+
+ public function test_seed_with_manually_selected_seeder(): void
+ {
+ $this->migrate(
+ CreateMigrationsTable::class,
+ CreatePublishersTable::class,
+ CreateAuthorTable::class,
+ CreateBookTable::class,
+ );
+
+ $this->console
+ ->call(sprintf('db:seed --seeder=%s', SecondTestDatabaseSeeder::class))
+ ->assertSuccess();
+
+ $this->assertSame(1, query(Book::class)->count()->execute());
+ $book = Book::get(1);
+ $this->assertSame('Timeline Taxi 2', $book->title);
+ }
+
+ public function test_migrate_fresh_seed_with_manually_selected_seeder(): void
+ {
+ $this->console
+ ->call(sprintf('migrate:fresh --seeder=%s', SecondTestDatabaseSeeder::class))
+ ->assertSuccess();
+
+ $this->assertSame(1, query(Book::class)->count()->execute());
+ $book = Book::get(1);
+ $this->assertSame('Timeline Taxi 2', $book->title);
+ }
+
+ public function test_seed_all(): void
+ {
+ $this->migrate(
+ CreateMigrationsTable::class,
+ CreatePublishersTable::class,
+ CreateAuthorTable::class,
+ CreateBookTable::class,
+ );
+
+ $this->console
+ ->call('db:seed --all')
+ ->assertSuccess();
+
+ $this->assertSame(2, query(Book::class)->count()->execute());
+
+ $book = Book::select()->whereField('title', 'Timeline Taxi')->first();
+ $this->assertNotNull($book);
+
+ $book = Book::select()->whereField('title', 'Timeline Taxi 2')->first();
+ $this->assertNotNull($book);
+ }
+
+ public function test_seed_when_only_one_seeder_is_available(): void
+ {
+ $seederConfig = $this->container->get(SeederConfig::class);
+ $seederConfig->seeders = [
+ TestDatabaseSeeder::class,
+ ];
+
+ $this->migrate(
+ CreateMigrationsTable::class,
+ CreatePublishersTable::class,
+ CreateAuthorTable::class,
+ CreateBookTable::class,
+ );
+
+ $this->console
+ ->call('db:seed')
+ ->assertSuccess();
+
+ $this->assertSame(1, query(Book::class)->count()->execute());
+ $book = Book::get(1);
+ $this->assertSame('Timeline Taxi', $book->title);
+ }
+
+ public function test_seed_via_migrate_fresh(): void
+ {
+ $this->console
+ ->call('migrate:fresh --seed --all')
+ ->assertSuccess();
+
+ $this->assertSame(2, query(Book::class)->count()->execute());
+
+ $book = Book::select()->whereField('title', 'Timeline Taxi')->first();
+ $this->assertNotNull($book);
+
+ $book = Book::select()->whereField('title', 'Timeline Taxi 2')->first();
+ $this->assertNotNull($book);
+ }
+
+ public function test_db_seed_caution(): void
+ {
+ $appConfig = $this->container->get(AppConfig::class);
+ $appConfig->environment = Environment::PRODUCTION;
+
+ $this->console
+ ->call('migrate:fresh --seed --all')
+ ->assertSee('Do you wish to continue');
+ }
+}