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: "{$seederClass}", + value: "SEEDED", + ); + } +} 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'); + } +}