Skip to content

Commit 8bbe427

Browse files
[9.x] Adds WithoutModelEvents trait for running seeds without Model Events (#39922)
* Adds `WithoutEvents` trait for application seeders * Renames `WithoutEvents` to `WithoutModelEvents` * Adjusts comment Co-authored-by: James Brooks <james@alt-three.com> Co-authored-by: James Brooks <james@alt-three.com>
1 parent c892c2c commit 8bbe427

File tree

3 files changed

+89
-3
lines changed

3 files changed

+89
-3
lines changed
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
namespace Illuminate\Database\Console\Seeds;
4+
5+
use Illuminate\Database\Eloquent\Model;
6+
7+
trait WithoutModelEvents
8+
{
9+
/**
10+
* Prevent model events from being dispatched by the given callback.
11+
*
12+
* @param callable $callback
13+
* @return callable
14+
*/
15+
public function withoutModelEvents(callable $callback)
16+
{
17+
return fn () => Model::withoutEvents($callback);
18+
}
19+
}

src/Illuminate/Database/Seeder.php

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use Illuminate\Console\Command;
66
use Illuminate\Container\Container;
7+
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
78
use Illuminate\Support\Arr;
89
use InvalidArgumentException;
910

@@ -170,8 +171,16 @@ public function __invoke(array $parameters = [])
170171
throw new InvalidArgumentException('Method [run] missing from '.get_class($this));
171172
}
172173

173-
return isset($this->container)
174-
? $this->container->call([$this, 'run'], $parameters)
175-
: $this->run(...$parameters);
174+
$callback = fn () => isset($this->container)
175+
? $this->container->call([$this, 'run'], $parameters)
176+
: $this->run(...$parameters);
177+
178+
$uses = array_flip(class_uses_recursive(static::class));
179+
180+
if (isset($uses[WithoutModelEvents::class])) {
181+
$callback = $this->withoutModelEvents($callback);
182+
}
183+
184+
return $callback();
176185
}
177186
}

tests/Database/SeedCommandTest.php

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,14 @@
44

55
use Illuminate\Console\OutputStyle;
66
use Illuminate\Container\Container;
7+
use Illuminate\Contracts\Events\Dispatcher;
78
use Illuminate\Database\ConnectionResolverInterface;
89
use Illuminate\Database\Console\Seeds\SeedCommand;
10+
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
11+
use Illuminate\Database\Eloquent\Model;
912
use Illuminate\Database\Seeder;
13+
use Illuminate\Events\NullDispatcher;
14+
use Illuminate\Testing\Assert;
1015
use Mockery as m;
1116
use PHPUnit\Framework\TestCase;
1217
use Symfony\Component\Console\Input\ArrayInput;
@@ -46,8 +51,61 @@ public function testHandle()
4651
$container->shouldHaveReceived('call')->with([$command, 'handle']);
4752
}
4853

54+
public function testWithoutModelEvents()
55+
{
56+
$input = new ArrayInput([
57+
'--force' => true,
58+
'--database' => 'sqlite',
59+
'--class' => UserWithoutModelEventsSeeder::class,
60+
]);
61+
$output = new NullOutput;
62+
63+
$instance = new UserWithoutModelEventsSeeder();
64+
65+
$seeder = m::mock($instance);
66+
$seeder->shouldReceive('setContainer')->once()->andReturnSelf();
67+
$seeder->shouldReceive('setCommand')->once()->andReturnSelf();
68+
69+
$resolver = m::mock(ConnectionResolverInterface::class);
70+
$resolver->shouldReceive('getDefaultConnection')->once();
71+
$resolver->shouldReceive('setDefaultConnection')->once()->with('sqlite');
72+
73+
$container = m::mock(Container::class);
74+
$container->shouldReceive('call');
75+
$container->shouldReceive('environment')->once()->andReturn('testing');
76+
$container->shouldReceive('make')->with(UserWithoutModelEventsSeeder::class)->andReturn($seeder);
77+
$container->shouldReceive('make')->with(OutputStyle::class, m::any())->andReturn(
78+
new OutputStyle($input, $output)
79+
);
80+
81+
$command = new SeedCommand($resolver);
82+
$command->setLaravel($container);
83+
84+
Model::setEventDispatcher($dispatcher = m::mock(Dispatcher::class));
85+
86+
// call run to set up IO, then fire manually.
87+
$command->run($input, $output);
88+
$command->handle();
89+
90+
Assert::assertSame($dispatcher, Model::getEventDispatcher());
91+
92+
$container->shouldHaveReceived('call')->with([$command, 'handle']);
93+
}
94+
4995
protected function tearDown(): void
5096
{
97+
Model::unsetEventDispatcher();
98+
5199
m::close();
52100
}
53101
}
102+
103+
class UserWithoutModelEventsSeeder extends Seeder
104+
{
105+
use WithoutModelEvents;
106+
107+
public function run()
108+
{
109+
Assert::assertInstanceOf(NullDispatcher::class, Model::getEventDispatcher());
110+
}
111+
}

0 commit comments

Comments
 (0)