Skip to content

Commit 910dc1d

Browse files
committed
Add --fake flag to seeds run and --seed option to seeds reset
- Add --fake flag to mark seeds as executed without running them - Add --seed option to seeds reset for selective seed reset - Both features mirror similar functionality in migrations
1 parent 1134bb1 commit 910dc1d

File tree

4 files changed

+182
-15
lines changed

4 files changed

+182
-15
lines changed

src/Command/SeedCommand.php

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,10 @@ public function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionPar
9393
'short' => 'f',
9494
'help' => 'Force re-running seeds that have already been executed',
9595
'boolean' => true,
96+
])
97+
->addOption('fake', [
98+
'help' => 'Mark seeds as executed without actually running them',
99+
'boolean' => true,
96100
]);
97101

98102
return $parser;
@@ -154,9 +158,14 @@ protected function executeSeeds(Arguments $args, ConsoleIo $io): ?int
154158

155159
$versionOrder = $config->getVersionOrder();
156160

161+
$fake = (bool)$args->getOption('fake');
162+
157163
if ($config->isDryRun()) {
158164
$io->info('DRY-RUN mode enabled');
159165
}
166+
if ($fake) {
167+
$io->warning('performing fake seeding');
168+
}
160169
$io->verbose('<info>using connection</info> ' . (string)$args->getOption('connection'));
161170
$io->verbose('<info>using paths</info> ' . $config->getMigrationPath());
162171
$io->verbose('<info>ordering by</info> ' . $versionOrder . ' time');
@@ -205,11 +214,11 @@ protected function executeSeeds(Arguments $args, ConsoleIo $io): ?int
205214
}
206215

207216
// run all the seed(ers)
208-
$manager->seed(null, (bool)$args->getOption('force'));
217+
$manager->seed(null, (bool)$args->getOption('force'), $fake);
209218
} else {
210219
// run seed(ers) specified as arguments
211220
foreach ($seeds as $seed) {
212-
$manager->seed(trim($seed), (bool)$args->getOption('force'));
221+
$manager->seed(trim($seed), (bool)$args->getOption('force'), $fake);
213222
}
214223
}
215224
$end = microtime(true);

src/Command/SeedResetCommand.php

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,12 @@ public function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionPar
4949
'allowing seeds to be re-run without the --force flag.',
5050
'',
5151
'<info>seeds reset</info>',
52+
'<info>seeds reset --seed Users</info>',
53+
'<info>seeds reset --seed Users,Posts</info>',
5254
'<info>seeds reset --plugin Demo</info>',
5355
'<info>seeds reset -c secondary</info>',
56+
])->addOption('seed', [
57+
'help' => 'Comma-separated list of specific seeds to reset. Resets all seeds if not specified.',
5458
])->addOption('plugin', [
5559
'short' => 'p',
5660
'help' => 'The plugin to reset seeds for',
@@ -100,9 +104,25 @@ public function execute(Arguments $args, ConsoleIo $io): ?int
100104
$seeds = $manager->getSeeds();
101105
$adapter = $manager->getEnvironment()->getAdapter();
102106

103-
// Reset all seeds
107+
// Filter seeds if --seed option is specified
108+
$seedOption = $args->getOption('seed');
104109
$seedsToReset = $seeds;
105110

111+
if ($seedOption) {
112+
$requestedSeeds = array_map('trim', explode(',', (string)$seedOption));
113+
$seedsToReset = [];
114+
115+
foreach ($requestedSeeds as $requestedSeed) {
116+
$normalizedName = $manager->normalizeSeedName($requestedSeed, $seeds);
117+
if ($normalizedName === null) {
118+
$io->error("Seed `{$requestedSeed}` does not exist.");
119+
120+
return self::CODE_ERROR;
121+
}
122+
$seedsToReset[$normalizedName] = $seeds[$normalizedName];
123+
}
124+
}
125+
106126
if (empty($seedsToReset)) {
107127
$io->warning('No seeds to reset.');
108128

@@ -111,7 +131,8 @@ public function execute(Arguments $args, ConsoleIo $io): ?int
111131

112132
// Show what will be reset and ask for confirmation
113133
$io->out('');
114-
$io->out('<info>All seeds will be reset:</info>');
134+
$resetAllMessage = $seedOption ? '<info>The following seeds will be reset:</info>' : '<info>All seeds will be reset:</info>';
135+
$io->out($resetAllMessage);
115136
foreach ($seedsToReset as $seed) {
116137
$io->out(' - ' . Util::getSeedDisplayName($seed->getName()));
117138
}

src/Migration/Manager.php

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -540,9 +540,10 @@ public function executeMigration(MigrationInterface $migration, string $directio
540540
*
541541
* @param \Migrations\SeedInterface $seed Seed
542542
* @param bool $force Force re-execution even if seed has already been executed
543+
* @param bool $fake Record seed as executed without actually running it
543544
* @return void
544545
*/
545-
public function executeSeed(SeedInterface $seed, bool $force = false): void
546+
public function executeSeed(SeedInterface $seed, bool $force = false, bool $fake = false): void
546547
{
547548
$this->getIo()->out('');
548549

@@ -560,6 +561,26 @@ public function executeSeed(SeedInterface $seed, bool $force = false): void
560561
return;
561562
}
562563

564+
// Ensure seed schema table exists
565+
$adapter = $this->getEnvironment()->getAdapter();
566+
if (!$adapter->hasTable($adapter->getSeedSchemaTableName())) {
567+
$adapter->createSeedSchemaTable();
568+
}
569+
570+
if ($fake) {
571+
// Record seed as executed without running it
572+
$this->printSeedStatus($seed, 'faking');
573+
574+
if (!$seed->isIdempotent()) {
575+
$executedTime = date('Y-m-d H:i:s');
576+
$adapter->seedExecuted($seed, $executedTime);
577+
}
578+
579+
$this->printSeedStatus($seed, 'faked');
580+
581+
return;
582+
}
583+
563584
// Auto-execute missing dependencies
564585
$missingDeps = $this->getSeedDependenciesNotExecuted($seed);
565586
if (!empty($missingDeps)) {
@@ -568,18 +589,12 @@ public function executeSeed(SeedInterface $seed, bool $force = false): void
568589
' Auto-executing dependency: %s',
569590
$depSeed->getName(),
570591
));
571-
$this->executeSeed($depSeed, $force);
592+
$this->executeSeed($depSeed, $force, $fake);
572593
}
573594
}
574595

575596
$this->printSeedStatus($seed, 'seeding');
576597

577-
// Ensure seed schema table exists
578-
$adapter = $this->getEnvironment()->getAdapter();
579-
if (!$adapter->hasTable($adapter->getSeedSchemaTableName())) {
580-
$adapter->createSeedSchemaTable();
581-
}
582-
583598
// Execute the seeder and log the time elapsed.
584599
$start = microtime(true);
585600
$this->getEnvironment()->executeSeed($seed);
@@ -794,25 +809,26 @@ public function rollback(int|string|null $target = null, bool $force = false, bo
794809
*
795810
* @param string|null $seed Seeder
796811
* @param bool $force Force re-execution even if seed has already been executed
812+
* @param bool $fake Record seed as executed without actually running it
797813
* @throws \InvalidArgumentException
798814
* @return void
799815
*/
800-
public function seed(?string $seed = null, bool $force = false): void
816+
public function seed(?string $seed = null, bool $force = false, bool $fake = false): void
801817
{
802818
$seeds = $this->getSeeds();
803819

804820
if ($seed === null) {
805821
// run all seeders
806822
foreach ($seeds as $seeder) {
807823
if (array_key_exists($seeder->getName(), $seeds)) {
808-
$this->executeSeed($seeder, $force);
824+
$this->executeSeed($seeder, $force, $fake);
809825
}
810826
}
811827
} else {
812828
// run only one seeder
813829
$normalizedName = $this->normalizeSeedName($seed, $seeds);
814830
if ($normalizedName !== null) {
815-
$this->executeSeed($seeds[$normalizedName], $force);
831+
$this->executeSeed($seeds[$normalizedName], $force, $fake);
816832
} else {
817833
throw new InvalidArgumentException(sprintf('The seed `%s` does not exist', $seed));
818834
}

tests/TestCase/Command/SeedCommandTest.php

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -570,4 +570,125 @@ public function testNonIdempotentSeedIsTracked(): void
570570
$this->assertOutputContains('already executed');
571571
$this->assertOutputNotContains('seeding');
572572
}
573+
574+
public function testFakeSeedMarksAsExecuted(): void
575+
{
576+
$this->createTables();
577+
578+
/** @var \Cake\Database\Connection $connection */
579+
$connection = ConnectionManager::get('test');
580+
581+
// Run with --fake flag
582+
$this->exec('seeds run -c test NumbersSeed --fake');
583+
$this->assertExitSuccess();
584+
$this->assertErrorContains('performing fake seeding');
585+
$this->assertOutputContains('faking');
586+
$this->assertOutputContains('faked');
587+
$this->assertOutputNotContains('seeding');
588+
589+
// Verify NO data was inserted
590+
$query = $connection->execute('SELECT COUNT(*) FROM numbers');
591+
$this->assertEquals(0, $query->fetchColumn(0), 'Fake seed should not insert data');
592+
593+
// Verify the seed WAS tracked in cake_seeds table
594+
$seedLog = $connection->execute('SELECT COUNT(*) FROM cake_seeds WHERE seed_name = \'NumbersSeed\'');
595+
$this->assertEquals(1, $seedLog->fetchColumn(0), 'Fake seeds should be tracked');
596+
597+
// Running again should show already executed
598+
$this->exec('seeds run -c test NumbersSeed');
599+
$this->assertExitSuccess();
600+
$this->assertOutputContains('already executed');
601+
}
602+
603+
public function testFakeSeedWithForce(): void
604+
{
605+
$this->createTables();
606+
607+
/** @var \Cake\Database\Connection $connection */
608+
$connection = ConnectionManager::get('test');
609+
610+
// Run with --fake first
611+
$this->exec('seeds run -c test NumbersSeed --fake');
612+
$this->assertExitSuccess();
613+
614+
// Verify seed is tracked
615+
$seedLog = $connection->execute('SELECT COUNT(*) FROM cake_seeds WHERE seed_name = \'NumbersSeed\'');
616+
$this->assertEquals(1, $seedLog->fetchColumn(0));
617+
618+
// Run with --force to actually execute it
619+
$this->exec('seeds run -c test NumbersSeed --force');
620+
$this->assertExitSuccess();
621+
$this->assertOutputContains('seeding');
622+
623+
// Verify data was inserted
624+
$query = $connection->execute('SELECT COUNT(*) FROM numbers');
625+
$this->assertEquals(1, $query->fetchColumn(0));
626+
}
627+
628+
public function testResetSpecificSeed(): void
629+
{
630+
$this->createTables();
631+
632+
/** @var \Cake\Database\Connection $connection */
633+
$connection = ConnectionManager::get('test');
634+
635+
// Run two seeds
636+
$this->exec('seeds run -c test NumbersSeed');
637+
$this->assertExitSuccess();
638+
639+
$this->exec('seeds run -c test StoresSeed');
640+
$this->assertExitSuccess();
641+
642+
// Verify both are tracked
643+
$numbersLog = $connection->execute('SELECT COUNT(*) FROM cake_seeds WHERE seed_name = \'NumbersSeed\'');
644+
$this->assertEquals(1, $numbersLog->fetchColumn(0));
645+
646+
$storesLog = $connection->execute('SELECT COUNT(*) FROM cake_seeds WHERE seed_name = \'StoresSeed\'');
647+
$this->assertEquals(1, $storesLog->fetchColumn(0));
648+
649+
// Reset only Numbers seed
650+
$this->exec('seeds reset -c test --seed Numbers', ['y']);
651+
$this->assertExitSuccess();
652+
$this->assertOutputContains('The following seeds will be reset:');
653+
$this->assertOutputNotContains('All seeds will be reset:');
654+
655+
// Verify Numbers is reset but Stores is still tracked
656+
$numbersLog = $connection->execute('SELECT COUNT(*) FROM cake_seeds WHERE seed_name = \'NumbersSeed\'');
657+
$this->assertEquals(0, $numbersLog->fetchColumn(0), 'Numbers seed should be reset');
658+
659+
$storesLog = $connection->execute('SELECT COUNT(*) FROM cake_seeds WHERE seed_name = \'StoresSeed\'');
660+
$this->assertEquals(1, $storesLog->fetchColumn(0), 'Stores seed should still be tracked');
661+
}
662+
663+
public function testResetMultipleSpecificSeeds(): void
664+
{
665+
$this->createTables();
666+
667+
/** @var \Cake\Database\Connection $connection */
668+
$connection = ConnectionManager::get('test');
669+
670+
// Run seeds
671+
$this->exec('seeds run -c test NumbersSeed');
672+
$this->exec('seeds run -c test StoresSeed');
673+
674+
// Reset both with comma-separated list
675+
$this->exec('seeds reset -c test --seed Numbers,Stores', ['y']);
676+
$this->assertExitSuccess();
677+
678+
// Verify both are reset
679+
$numbersLog = $connection->execute('SELECT COUNT(*) FROM cake_seeds WHERE seed_name = \'NumbersSeed\'');
680+
$this->assertEquals(0, $numbersLog->fetchColumn(0));
681+
682+
$storesLog = $connection->execute('SELECT COUNT(*) FROM cake_seeds WHERE seed_name = \'StoresSeed\'');
683+
$this->assertEquals(0, $storesLog->fetchColumn(0));
684+
}
685+
686+
public function testResetNonExistentSeed(): void
687+
{
688+
$this->createTables();
689+
690+
$this->exec('seeds reset -c test --seed NonExistent');
691+
$this->assertExitError();
692+
$this->assertErrorContains('Seed `NonExistent` does not exist');
693+
}
573694
}

0 commit comments

Comments
 (0)