diff --git a/src/Illuminate/Database/Console/Seeds/SeedCommand.php b/src/Illuminate/Database/Console/Seeds/SeedCommand.php index 515ff410b30c..bf7821d68875 100644 --- a/src/Illuminate/Database/Console/Seeds/SeedCommand.php +++ b/src/Illuminate/Database/Console/Seeds/SeedCommand.php @@ -7,6 +7,7 @@ use Illuminate\Console\Prohibitable; use Illuminate\Database\ConnectionResolverInterface as Resolver; use Illuminate\Database\Eloquent\Model; +use Illuminate\Support\Arr; use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputOption; @@ -67,9 +68,9 @@ public function handle() $this->resolver->setDefaultConnection($this->getDatabase()); - Model::unguarded(function () { - $this->getSeeder()->__invoke(); - }); + foreach ($this->getSeeders() as $seeder) { + Model::unguarded(fn () => $seeder->__invoke()); + } if ($previousConnection) { $this->resolver->setDefaultConnection($previousConnection); @@ -79,14 +80,29 @@ public function handle() } /** - * Get a seeder instance from the container. + * Get a number of seeder instances from the container. * * @return \Illuminate\Database\Seeder */ - protected function getSeeder() + protected function getSeeders() { - $class = $this->input->getArgument('class') ?? $this->input->getOption('class'); + $seeders = []; + + foreach ($this->getClasses() as $class) { + $seeders[] = $this->getSeeder($class); + } + + return $seeders; + } + /** + * Get a seeder instance from a class. + * + * @param string $class + * @return \Illuminate\Database\Seeder + */ + protected function getSeeder($class) + { if (! str_contains($class, '\\')) { $class = 'Database\\Seeders\\'.$class; } @@ -101,6 +117,22 @@ protected function getSeeder() ->setCommand($this); } + /** + * Get seeder classes. + * + * @return array + */ + protected function getClasses() + { + $classes = $this->input->getArgument('class') ?? $this->input->getOption('class'); + + if (str_contains($classes, ',')) { + return explode(',', $classes); + } + + return Arr::wrap($classes); + } + /** * Get the name of the database connection to use. * @@ -133,7 +165,7 @@ protected function getArguments() protected function getOptions() { return [ - ['class', null, InputOption::VALUE_OPTIONAL, 'The class name of the root seeder', 'Database\\Seeders\\DatabaseSeeder'], + ['class', null, InputOption::VALUE_OPTIONAL, 'The class names of a number of seeders', 'Database\\Seeders\\DatabaseSeeder'], ['database', null, InputOption::VALUE_OPTIONAL, 'The database connection to seed'], ['force', null, InputOption::VALUE_NONE, 'Force the operation to run when in production'], ]; diff --git a/tests/Database/SeedCommandTest.php b/tests/Database/SeedCommandTest.php index aa9a8d25aa63..8afa0ef03a18 100644 --- a/tests/Database/SeedCommandTest.php +++ b/tests/Database/SeedCommandTest.php @@ -104,6 +104,59 @@ public function testWithoutModelEvents() $container->shouldHaveReceived('call')->with([$command, 'handle']); } + public function testClassOptionWithMultipleSeeders() + { + $input = new ArrayInput([ + '--force' => true, + '--database' => 'sqlite', + '--class' => UserWithoutModelEventsSeeder::class.','.AnotherUserWithoutModelEventsSeeder::class, + ]); + $output = new NullOutput; + $outputStyle = new OutputStyle($input, $output); + + $firstInstance = new UserWithoutModelEventsSeeder(); + + $firstSeeder = m::mock($firstInstance); + $firstSeeder->shouldReceive('setContainer')->once()->andReturnSelf(); + $firstSeeder->shouldReceive('setCommand')->once()->andReturnSelf(); + + $secondInstance = new AnotherUserWithoutModelEventsSeeder(); + + $secondSeeder = m::mock($secondInstance); + $secondSeeder->shouldReceive('setContainer')->once()->andReturnSelf(); + $secondSeeder->shouldReceive('setCommand')->once()->andReturnSelf(); + + $resolver = m::mock(ConnectionResolverInterface::class); + $resolver->shouldReceive('getDefaultConnection')->once(); + $resolver->shouldReceive('setDefaultConnection')->once()->with('sqlite'); + + $container = m::mock(Container::class); + $container->shouldReceive('call'); + $container->shouldReceive('environment')->once()->andReturn('testing'); + $container->shouldReceive('runningUnitTests')->andReturn('true'); + $container->shouldReceive('make')->with(UserWithoutModelEventsSeeder::class)->andReturn($firstSeeder); + $container->shouldReceive('make')->with(AnotherUserWithoutModelEventsSeeder::class)->andReturn($secondSeeder); + $container->shouldReceive('make')->with(OutputStyle::class, m::any())->andReturn( + $outputStyle + ); + $container->shouldReceive('make')->with(Factory::class, m::any())->andReturn( + new Factory($outputStyle) + ); + + $command = new SeedCommand($resolver); + $command->setLaravel($container); + + Model::setEventDispatcher($dispatcher = m::mock(Dispatcher::class)); + + // call run to set up IO, then fire manually. + $command->run($input, $output); + $command->handle(); + + Assert::assertSame($dispatcher, Model::getEventDispatcher()); + + $container->shouldHaveReceived('call')->with([$command, 'handle']); + } + public function testProhibitable() { $input = new ArrayInput([]); @@ -152,3 +205,13 @@ public function run() Assert::assertInstanceOf(NullDispatcher::class, Model::getEventDispatcher()); } } + +class AnotherUserWithoutModelEventsSeeder extends Seeder +{ + use WithoutModelEvents; + + public function run() + { + Assert::assertInstanceOf(NullDispatcher::class, Model::getEventDispatcher()); + } +}