Passed
Push — master ( 14db2b...b42c42 )
by Kevin
02:47
created

Factory::isBooted()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
c 1
b 0
f 0
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 300
    final public static function boot(Configuration $configuration): void
147
    {
148 300
        self::$configuration = $configuration;
149 300
    }
150
151
    /**
152
     * @internal
153
     */
154 290
    final public static function configuration(): Configuration
155
    {
156 290
        if (!self::isBooted()) {
157
            throw new \RuntimeException('Foundry is not yet booted, is the ZenstruckFoundryBundle enabled/configured?');
158
        }
159
160 290
        return self::$configuration;
0 ignored issues
show
Bug Best Practice introduced by
The expression return self::configuration could return the type null which is incompatible with the type-hinted return Zenstruck\Foundry\Configuration. Consider adding an additional type-check to rule them out.
Loading history...
161
    }
162
163
    /**
164
     * @internal
165
     */
166 316
    final public static function isBooted(): bool
167
    {
168 316
        return null !== self::$configuration;
169
    }
170
171 214
    final public static function faker(): Faker\Generator
172
    {
173 214
        return self::configuration()->faker();
174
    }
175
176
    /**
177
     * @param array|callable $attributes
178
     */
179 262
    private static function normalizeAttributes($attributes): array
180
    {
181 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...
182
    }
183
184
    /**
185
     * @param array|callable $attributes
186
     */
187 262
    private function instantiate($attributes): object
188
    {
189
        // merge the factory attribute set with the passed attributes
190 262
        $attributeSet = \array_merge($this->attributeSet, [$attributes]);
191
192
        // normalize each attribute set and collapse
193 262
        $attributes = \array_merge(...\array_map([$this, 'normalizeAttributes'], $attributeSet));
194
195 262
        foreach ($this->beforeInstantiate as $callback) {
196 8
            $attributes = $callback($attributes);
197
198 8
            if (!\is_array($attributes)) {
199 4
                throw new \LogicException('Before Instantiate event callback must return an array.');
200
            }
201
        }
202
203
        // filter each attribute to convert proxies and factories to objects
204 258
        $attributes = \array_map(
205
            function($value) {
206 246
                return $this->normalizeAttribute($value);
207 258
            },
208 258
            $attributes
209
        );
210
211
        // instantiate the object with the users instantiator or if not set, the default instantiator
212 258
        $object = ($this->instantiator ?? self::configuration()->instantiator())($attributes, $this->class);
213
214 258
        foreach ($this->afterInstantiate as $callback) {
215 4
            $callback($object, $attributes);
216
        }
217
218 258
        return $object;
219
    }
220
221
    /**
222
     * @param mixed $value
223
     *
224
     * @return mixed
225
     */
226 246
    private function normalizeAttribute($value)
227
    {
228 246
        if ($value instanceof Proxy) {
229 24
            return $value->object();
230
        }
231
232 246
        if (\is_array($value)) {
233
            // possible OneToMany/ManyToMany relationship
234 12
            return \array_map(
235
                function($value) {
236 12
                    return $this->normalizeAttribute($value);
237 12
                },
238 12
                $value
239
            );
240
        }
241
242 246
        if (!$value instanceof self) {
243 246
            return $value;
244
        }
245
246 24
        if (!$this->persist) {
247
            // ensure attribute Factory's are also not persisted
248 4
            $value = $value->withoutPersisting();
249
        }
250
251 24
        return $value->create()->object();
252
    }
253
}
254