Skip to content

Commit

Permalink
[9.x] Allow factories to recycle multiple models of a given type (#44328
Browse files Browse the repository at this point in the history
)

* Eloquent Factories: Allow for recycling multiple models of a given type

* Eloquent Factories: Fix code styling

* Eloquent Factories: Fix code styling
  • Loading branch information
michal3377 authored Oct 3, 2022
1 parent bf43e5a commit 50a1577
Show file tree
Hide file tree
Showing 3 changed files with 89 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ protected function resolver($key)
return function () use ($key) {
if (! $this->resolved) {
$instance = $this->factory instanceof Factory
? ($this->factory->recycle->get($this->factory->modelName()) ?? $this->factory->create())
? ($this->factory->getRandomRecycledModel($this->factory->modelName()) ?? $this->factory->create())
: $this->factory;

return $this->resolved = $key ? $instance->{$key} : $instance->getKey();
Expand Down
31 changes: 22 additions & 9 deletions src/Illuminate/Database/Eloquent/Factories/Factory.php
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ abstract class Factory
*
* @var \Illuminate\Support\Collection
*/
public $recycle;
protected $recycle;

/**
* The "after making" callbacks that will be applied to the model.
Expand Down Expand Up @@ -464,9 +464,8 @@ protected function expandAttributes(array $definition)
return collect($definition)
->map($evaluateRelations = function ($attribute) {
if ($attribute instanceof self) {
$attribute = $this->recycle->has($attribute->modelName())
? $this->recycle->get($attribute->modelName())
: $attribute->recycle($this->recycle)->create()->getKey();
$attribute = $this->getRandomRecycledModel($attribute->modelName())
?? $attribute->recycle($this->recycle)->create()->getKey();
} elseif ($attribute instanceof Model) {
$attribute = $attribute->getKey();
}
Expand Down Expand Up @@ -619,21 +618,35 @@ public function for($factory, $relationship = null)
}

/**
* Provide a model instance to use instead of any nested factory calls when creating relationships.
* Provide model instances to use instead of any nested factory calls when creating relationships.
*
* @param \Illuminate\Eloquent\Model|\Illuminate\Support\Collection|array $model
* @return static
*/
public function recycle($model)
{
// Group provided models by the type and merge them into existing recycle collection
return $this->newInstance([
'recycle' => $this->recycle->merge(
Collection::wrap($model instanceof Model ? func_get_args() : $model)
->keyBy(fn ($model) => get_class($model))
),
'recycle' => $this->recycle
->flatten()
->merge(
Collection::wrap($model instanceof Model ? func_get_args() : $model)
->flatten()
)->groupBy(fn ($model) => get_class($model)),
]);
}

/**
* Retrieves a random model of a given type from previously provided models to recycle.
*
* @param string $modelClassName
* @return \Illuminate\Database\Eloquent\Model|null
*/
public function getRandomRecycledModel($modelClassName)
{
return $this->recycle->get($modelClassName)?->random();
}

/**
* Add a new "after making" callback to the model definition.
*
Expand Down
66 changes: 66 additions & 0 deletions tests/Database/DatabaseEloquentFactoryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -701,6 +701,72 @@ public function test_has_method_does_not_reassign_the_parent()
$this->assertSame(2, FactoryTestPost::count());
}

public function test_multiple_models_can_be_provided_to_recycle()
{
Factory::guessFactoryNamesUsing(function ($model) {
return $model.'Factory';
});

$users = FactoryTestUserFactory::new()->count(3)->create();

$posts = FactoryTestPostFactory::new()
->recycle($users)
->for(FactoryTestUserFactory::new())
->has(FactoryTestCommentFactory::new()->count(5), 'comments')
->count(2)
->create();

$this->assertSame(3, FactoryTestUser::count());
}

public function test_recycled_models_can_be_combined_with_multiple_calls()
{
Factory::guessFactoryNamesUsing(function ($model) {
return $model.'Factory';
});

$users = FactoryTestUserFactory::new()
->count(2)
->create();
$posts = FactoryTestPostFactory::new()
->recycle($users)
->count(2)
->create();
$additionalUser = FactoryTestUserFactory::new()
->create();
$additionalPost = FactoryTestPostFactory::new()
->recycle($additionalUser)
->create();

$this->assertSame(3, FactoryTestUser::count());
$this->assertSame(3, FactoryTestPost::count());

$comments = FactoryTestCommentFactory::new()
->recycle($users)
->recycle($posts)
->recycle([$additionalUser, $additionalPost])
->count(5)
->create();

$this->assertSame(3, FactoryTestUser::count());
$this->assertSame(3, FactoryTestPost::count());
}

public function test_no_models_can_be_provided_to_recycle()
{
Factory::guessFactoryNamesUsing(function ($model) {
return $model.'Factory';
});

$posts = FactoryTestPostFactory::new()
->recycle([])
->count(2)
->create();

$this->assertSame(2, FactoryTestPost::count());
$this->assertSame(2, FactoryTestUser::count());
}

/**
* Get a database connection instance.
*
Expand Down

0 comments on commit 50a1577

Please sign in to comment.