Skip to content

Commit 2fb4545

Browse files
authored
Feature: Add deferred resolution to model factory (#14)
1 parent ac30397 commit 2fb4545

File tree

4 files changed

+517
-2
lines changed

4 files changed

+517
-2
lines changed

src/Models/ModelFactory.md

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
# Model Factory
2+
3+
## Introduction
4+
5+
Factory classes are used to programmatically create instances of models. This is useful for seeding databases, creating test data, and other situations where you need to create a lot of model instances.
6+
7+
```php
8+
<?php
9+
10+
namespace Give\Campaigns\Factories;
11+
12+
class CampaignFactory extends Give\Framework\Models\Factories\ModelFactory
13+
{
14+
public function definition(): array
15+
{
16+
return [
17+
'title' => __('GiveWP Campaign', 'give'),
18+
'description' => $this->faker->paragraph(),
19+
];
20+
}
21+
}
22+
```
23+
24+
Each instance of a model created by a factory class is populated with default values defined in the `definition` method, which can be hard-coded or generated dynamically using integrated the `fakerphp/faker` library.
25+
26+
## Creating Models with Factories
27+
28+
A model can be instantiated using the factory `make()` method, which uses the defaults provided by the `definition()` method to create the model instance.
29+
30+
```php
31+
use Give\Campaigns\Models\Campaign;
32+
33+
$campaign = Campaign::factory()->make();
34+
```
35+
36+
Additionally, multiple model instances can be created using the `count()` method.
37+
38+
```php
39+
use Give\Campaigns\Models\Campaign;
40+
41+
$campaigns = Campaign::factory()->count(3)->make();
42+
```
43+
44+
### Overriding Attributes
45+
46+
You can override these default values by passing an array of attributes to the factory's `make()` or `create()` method.
47+
48+
```php
49+
use Give\Campaigns\Models\Campaign;
50+
51+
$campaign Campaign::factory()->create([
52+
'title' => 'My Custom Campaign',
53+
]);
54+
```
55+
56+
### Persisting Models
57+
58+
The `create()` method instantiates model instances and persists them to the database using model's `save()` method.
59+
60+
```php
61+
use Give\Campaigns\Models\Campaign;
62+
63+
$campaign Campaign::factory()->create();
64+
```
65+
66+
### Deferred Resolution
67+
68+
Sometimes you may need to defer the resolution of an attribute until the model is being created. Either the attribute definition is not a simple value or is otherwise expensive to instantiate.
69+
70+
Factory attribute definitions can be deferred using `Closure` callbacks instead of a hard-coded or generated value.
71+
72+
```php
73+
public function definition(): array
74+
{
75+
return [
76+
'title' => __('GiveWP Campaign', 'give'),
77+
'description' => function() {
78+
return prompt('Write a short description for a fundraising campaign.')
79+
},
80+
];
81+
}
82+
```
83+
84+
Additionally, deferred attributes are not resolved when the attribute is overridden, which prevents unnecessary computation when the default value is not used.
85+
86+
```php
87+
$campaign = Campaign::factory()->create([
88+
'description' => 'My custom description',
89+
]);
90+
```
91+
92+
## Model Relationships with Factories
93+
94+
Attribute definitions can also be other model factories, which can be resolved to a property value, such as an ID, to create required dependencies.
95+
96+
```php
97+
public function definition(): array
98+
{
99+
return [
100+
'title' => __('GiveWP Campaign', 'give'),
101+
'formId' => DonationForm::factory()->createAndResolveTo('id'),
102+
];
103+
}
104+
```
105+
106+
This is particularly useful when defining model relationships, where the related model is only instantiated when a model is not explicity provided as an override.
107+
108+
If an existing model (or model ID) is provided as an override, the factory will not instantiate an additional model.
109+
110+
```php
111+
Campaign::factory()->create([
112+
'formId' => $donationForm->id,
113+
]);
114+
```

src/Models/ModelFactory.php

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace StellarWP\Models;
44

5+
use Closure;
56
use Exception;
67
use StellarWP\DB\DB;
78

@@ -53,6 +54,18 @@ public function make( array $attributes = [] ) {
5354
return $this->count === 1 ? $results[0] : $results;
5455
}
5556

57+
/**
58+
* @since 1.2.3
59+
*/
60+
public function makeAndResolveTo($property): Closure
61+
{
62+
return function() use ($property) {
63+
return is_array($results = $this->make())
64+
? array_column($results, $property)
65+
: $results->$property;
66+
};
67+
}
68+
5669
/**
5770
* @since 1.0.0
5871
*
@@ -74,15 +87,34 @@ public function create( array $attributes = [] ) {
7487
return $this->count === 1 ? $instances[0] : $instances;
7588
}
7689

90+
/**
91+
* @since 1.2.3
92+
*/
93+
public function createAndResolveTo( $property ): Closure {
94+
return function() use ( $property ) {
95+
return is_array( $results = $this->create() )
96+
? array_column( $results, $property )
97+
: $results->$property;
98+
};
99+
}
100+
77101
/**
78102
* Creates an instance of the model from the attributes and definition.
79103
*
104+
* @since 1.2.3 Add support for resolving Closures.
80105
* @since 1.0.0
81106
*
82107
* @return M
83108
*/
84109
protected function makeInstance( array $attributes ) {
85-
return new $this->model( array_merge( $this->definition(), $attributes ) );
110+
return new $this->model(
111+
array_map(
112+
function( $attribute ) {
113+
return $attribute instanceof Closure ? $attribute() : $attribute;
114+
},
115+
array_merge( $this->definition(), $attributes )
116+
)
117+
);
86118
}
87119

88120
/**

0 commit comments

Comments
 (0)