Skip to content

Commit

Permalink
[9.x] Allow using a model instance in place of nested model factories (
Browse files Browse the repository at this point in the history
…laravel#44107)

* Allow passing a model instance to use in place of nested model factories

* formatting

* Apply fixes from StyleCI

* recycle

Co-authored-by: Taylor Otwell <[email protected]>
Co-authored-by: StyleCI Bot <[email protected]>
  • Loading branch information
3 people authored Sep 13, 2022
1 parent 8d1bfc1 commit 5bc3c99
Show file tree
Hide file tree
Showing 5 changed files with 96 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,19 @@ public function createFor(Model $model)
);
});
}

/**
* Specify the model instances to always use when creating relationships.
*
* @param \Illuminate\Support\Collection $recycle
* @return $this
*/
public function recycle($recycle)
{
if ($this->factory instanceof Factory) {
$this->factory = $this->factory->recycle($recycle);
}

return $this;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,19 @@ protected function resolver($key)
return $this->resolved;
};
}

/**
* Specify the model instances to always use when creating relationships.
*
* @param \Illuminate\Support\Collection $recycle
* @return $this
*/
public function recycle($recycle)
{
if ($this->factory instanceof Factory) {
$this->factory = $this->factory->recycle($recycle);
}

return $this;
}
}
37 changes: 33 additions & 4 deletions src/Illuminate/Database/Eloquent/Factories/Factory.php
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,13 @@ abstract class Factory
*/
protected $for;

/**
* The model instances to always use when creating relationships.
*
* @var \Illuminate\Support\Collection
*/
protected $recycle;

/**
* The "after making" callbacks that will be applied to the model.
*
Expand Down Expand Up @@ -122,6 +129,7 @@ abstract class Factory
* @param \Illuminate\Support\Collection|null $afterMaking
* @param \Illuminate\Support\Collection|null $afterCreating
* @param string|null $connection
* @param \Illuminate\Support\Collection|null $recycle
* @return void
*/
public function __construct($count = null,
Expand All @@ -130,7 +138,8 @@ public function __construct($count = null,
?Collection $for = null,
?Collection $afterMaking = null,
?Collection $afterCreating = null,
$connection = null)
$connection = null,
?Collection $recycle = null)
{
$this->count = $count;
$this->states = $states ?? new Collection;
Expand All @@ -139,6 +148,7 @@ public function __construct($count = null,
$this->afterMaking = $afterMaking ?? new Collection;
$this->afterCreating = $afterCreating ?? new Collection;
$this->connection = $connection;
$this->recycle = $recycle ?? new Collection;
$this->faker = $this->withFaker();
}

Expand Down Expand Up @@ -332,7 +342,7 @@ protected function createChildren(Model $model)
{
Model::unguarded(function () use ($model) {
$this->has->each(function ($has) use ($model) {
$has->createFor($model);
$has->recycle($this->recycle)->createFor($model);
});
});
}
Expand Down Expand Up @@ -439,7 +449,7 @@ protected function parentResolvers()
$model = $this->newModel();

return $this->for->map(function (BelongsToRelationship $for) use ($model) {
return $for->attributesFor($model);
return $for->recycle($this->recycle)->attributesFor($model);
})->collapse()->all();
}

Expand All @@ -454,7 +464,9 @@ protected function expandAttributes(array $definition)
return collect($definition)
->map($evaluateRelations = function ($attribute) {
if ($attribute instanceof self) {
$attribute = $attribute->create()->getKey();
$attribute = $this->recycle->has($attribute->modelName())
? $this->recycle->get($attribute->modelName())
: $attribute->recycle($this->recycle)->create()->getKey();
} elseif ($attribute instanceof Model) {
$attribute = $attribute->getKey();
}
Expand Down Expand Up @@ -606,6 +618,22 @@ public function for($factory, $relationship = null)
)])]);
}

/**
* Provide a model instance 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)
{
return $this->newInstance([
'recycle' => $this->recycle->merge(
Collection::wrap($model instanceof Model ? func_get_args() : $model)
->keyBy(fn ($model) => get_class($model))
),
]);
}

/**
* Add a new "after making" callback to the model definition.
*
Expand Down Expand Up @@ -697,6 +725,7 @@ protected function newInstance(array $arguments = [])
'afterMaking' => $this->afterMaking,
'afterCreating' => $this->afterCreating,
'connection' => $this->connection,
'recycle' => $this->recycle,
], $arguments)));
}

Expand Down
13 changes: 13 additions & 0 deletions src/Illuminate/Database/Eloquent/Factories/Relationship.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,17 @@ public function createFor(Model $parent)
$relationship->attach($this->factory->create([], $parent));
}
}

/**
* Specify the model instances to always use when creating relationships.
*
* @param \Illuminate\Support\Collection $recycle
* @return $this
*/
public function recycle($recycle)
{
$this->factory = $this->factory->recycle($recycle);

return $this;
}
}
20 changes: 20 additions & 0 deletions tests/Database/DatabaseEloquentFactoryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ public function createSchema()
$table->increments('id');
$table->foreignId('commentable_id');
$table->string('commentable_type');
$table->foreignId('user_id');
$table->string('body');
$table->softDeletes();
$table->timestamps();
Expand Down Expand Up @@ -651,6 +652,24 @@ public function test_dynamic_trashed_state_throws_exception_when_not_a_softdelet
FactoryTestUserFactory::new()->trashed()->create();
}

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

$user = FactoryTestUserFactory::new()->create();
$post = FactoryTestPostFactory::new()
->recycle($user)
->hasComments(2)
->create();

$this->assertSame(1, FactoryTestUser::count());
$this->assertEquals($user->id, $post->user_id);
$this->assertEquals($user->id, $post->comments[0]->user_id);
$this->assertEquals($user->id, $post->comments[1]->user_id);
}

/**
* Get a database connection instance.
*
Expand Down Expand Up @@ -756,6 +775,7 @@ public function definition()
return [
'commentable_id' => FactoryTestPostFactory::new(),
'commentable_type' => FactoryTestPost::class,
'user_id' => FactoryTestUserFactory::new(),
'body' => $this->faker->name,
];
}
Expand Down

0 comments on commit 5bc3c99

Please sign in to comment.