Passed
Pull Request — master (#19)
by Kevin
03:46
created

Factory   A

Complexity

Total Complexity 26

Size/Duplication

Total Lines 234
Duplicated Lines 0 %

Test Coverage

Coverage 98.73%

Importance

Changes 2
Bugs 0 Features 1
Metric Value
eloc 71
c 2
b 0
f 1
dl 0
loc 234
rs 10
ccs 78
cts 79
cp 0.9873
wmc 26

15 Methods

Rating   Name   Duplication   Size   Complexity  
A afterPersist() 0 6 1
A afterInstantiate() 0 6 1
A beforeInstantiate() 0 6 1
A withAttributes() 0 6 1
A __construct() 0 4 1
A withoutPersisting() 0 6 1
A configuration() 0 7 2
A normalizeAttributes() 0 3 2
A normalizeAttribute() 0 26 5
A faker() 0 3 1
A createMany() 0 7 1
A boot() 0 3 1
A instantiateWith() 0 6 1
A instantiate() 0 32 4
A create() 0 11 3
1
<?php
2
3
namespace Zenstruck\Foundry;
4
5
use Faker;
6
7
/**
8
 * @author Kevin Bond <[email protected]>
9
 */
10
class Factory
11
{
12
    /** @var Configuration|null */
13
    private static $configuration;
14
15
    /** @var string */
16
    private $class;
17
18
    /** @var callable|null */
19
    private $instantiator;
20
21
    /** @var bool */
22
    private $persist = true;
23
24
    /** @var array<array|callable> */
25
    private $attributeSet = [];
26
27
    /** @var callable[] */
28
    private $beforeInstantiate = [];
29
30
    /** @var callable[] */
31
    private $afterInstantiate = [];
32
33
    /** @var callable[] */
34
    private $afterPersist = [];
35
36
    /**
37
     * @param array|callable $defaultAttributes
38
     */
39 266
    public function __construct(string $class, $defaultAttributes = [])
40
    {
41 266
        $this->class = $class;
42 266
        $this->attributeSet[] = $defaultAttributes;
43 266
    }
44
45
    /**
46
     * @param array|callable $attributes
47
     *
48
     * @return Proxy|object
49
     */
50 262
    final public function create($attributes = []): Proxy
51
    {
52 262
        $proxy = new Proxy($this->instantiate($attributes));
53
54 258
        if (!$this->persist) {
55 56
            return $proxy;
56
        }
57
58
        return $proxy->save()->withoutAutoRefresh(function(Proxy $proxy) use ($attributes) {
59 202
            foreach ($this->afterPersist as $callback) {
60 4
                $proxy->executeCallback($callback, $attributes);
61
            }
62 202
        });
63
    }
64
65
    /**
66
     * @param array|callable $attributes
67
     *
68
     * @return Proxy[]|object[]
69
     */
70 60
    final public function createMany(int $number, $attributes = []): array
71
    {
72 60
        return \array_map(
73
            function() use ($attributes) {
74 60
                return $this->create($attributes);
75 60
            },
76 60
            \array_fill(0, $number, null)
77
        );
78
    }
79
80 64
    public function withoutPersisting(): self
81
    {
82 64
        $cloned = clone $this;
83 64
        $cloned->persist = false;
84
85 64
        return $cloned;
86
    }
87
88
    /**
89
     * @param array|callable $attributes
90
     */
91 210
    final public function withAttributes($attributes = []): self
92
    {
93 210
        $cloned = clone $this;
94 210
        $cloned->attributeSet[] = $attributes;
95
96 210
        return $cloned;
97
    }
98
99
    /**
100
     * @param callable $callback (array $attributes): array
101
     */
102 12
    final public function beforeInstantiate(callable $callback): self
103
    {
104 12
        $cloned = clone $this;
105 12
        $cloned->beforeInstantiate[] = $callback;
106
107 12
        return $cloned;
108
    }
109
110
    /**
111
     * @param callable $callback (object $object, array $attributes): void
112
     */
113 8
    final public function afterInstantiate(callable $callback): self
114
    {
115 8
        $cloned = clone $this;
116 8
        $cloned->afterInstantiate[] = $callback;
117
118 8
        return $cloned;
119
    }
120
121
    /**
122
     * @param callable $callback (object|Proxy $object, array $attributes): void
123
     */
124 8
    final public function afterPersist(callable $callback): self
125
    {
126 8
        $cloned = clone $this;
127 8
        $cloned->afterPersist[] = $callback;
128
129 8
        return $cloned;
130
    }
131
132
    /**
133
     * @param callable $instantiator (array $attributes, string $class): object
134
     */
135 8
    final public function instantiateWith(callable $instantiator): self
136
    {
137 8
        $cloned = clone $this;
138 8
        $cloned->instantiator = $instantiator;
139
140 8
        return $cloned;
141
    }
142
143
    /**
144
     * @internal
145
     */
146 324
    final public static function boot(Configuration $configuration): void
147
    {
148 324
        self::$configuration = $configuration;
149 324
    }
150
151
    /**
152
     * @internal
153
     */
154 290
    final public static function configuration(): Configuration
155
    {
156 290
        if (!self::$configuration) {
157
            throw new \RuntimeException('Foundry is not yet booted, is the ZenstruckFoundryBundle installed/configured?');
158
        }
159
160 290
        return self::$configuration;
161
    }
162
163 214
    final public static function faker(): Faker\Generator
164
    {
165 214
        return self::configuration()->faker();
166
    }
167
168
    /**
169
     * @param array|callable $attributes
170
     */
171 262
    private static function normalizeAttributes($attributes): array
172
    {
173 262
        return \is_callable($attributes) ? $attributes(self::faker()) : $attributes;
0 ignored issues
show
Bug Best Practice introduced by
The expression return is_callable($attr...:faker()) : $attributes could return the type callable which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
174
    }
175
176
    /**
177
     * @param array|callable $attributes
178
     */
179 262
    private function instantiate($attributes): object
180
    {
181
        // merge the factory attribute set with the passed attributes
182 262
        $attributeSet = \array_merge($this->attributeSet, [$attributes]);
183
184
        // normalize each attribute set and collapse
185 262
        $attributes = \array_merge(...\array_map([$this, 'normalizeAttributes'], $attributeSet));
186
187 262
        foreach ($this->beforeInstantiate as $callback) {
188 8
            $attributes = $callback($attributes);
189
190 8
            if (!\is_array($attributes)) {
191 4
                throw new \LogicException('Before Instantiate event callback must return an array.');
192
            }
193
        }
194
195
        // filter each attribute to convert proxies and factories to objects
196 258
        $attributes = \array_map(
197
            function($value) {
198 246
                return $this->normalizeAttribute($value);
199 258
            },
200 258
            $attributes
201
        );
202
203
        // instantiate the object with the users instantiator or if not set, the default instantiator
204 258
        $object = ($this->instantiator ?? self::configuration()->instantiator())($attributes, $this->class);
205
206 258
        foreach ($this->afterInstantiate as $callback) {
207 4
            $callback($object, $attributes);
208
        }
209
210 258
        return $object;
211
    }
212
213
    /**
214
     * @param mixed $value
215
     *
216
     * @return mixed
217
     */
218 246
    private function normalizeAttribute($value)
219
    {
220 246
        if ($value instanceof Proxy) {
221 24
            return $value->object();
222
        }
223
224 246
        if (\is_array($value)) {
225
            // possible OneToMany/ManyToMany relationship
226 12
            return \array_map(
227
                function($value) {
228 12
                    return $this->normalizeAttribute($value);
229 12
                },
230 12
                $value
231
            );
232
        }
233
234 246
        if (!$value instanceof self) {
235 246
            return $value;
236
        }
237
238 24
        if (!$this->persist) {
239
            // ensure attribute Factory's are also not persisted
240 4
            $value = $value->withoutPersisting();
241
        }
242
243 24
        return $value->create()->object();
244
    }
245
}
246