Passed
Pull Request — master (#1)
by Kevin
02:38
created

Factory::normalizeAttribute()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 26
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 5

Importance

Changes 0
Metric Value
cc 5
eloc 12
c 0
b 0
f 0
nc 5
nop 1
dl 0
loc 26
rs 9.5555
ccs 13
cts 13
cp 1
crap 5
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 246
    public function __construct(string $class, $defaultAttributes = [])
40
    {
41 246
        $this->class = $class;
42 246
        $this->attributeSet[] = $defaultAttributes;
43 246
    }
44
45
    /**
46
     * @param array|callable $attributes
47
     *
48
     * @return Proxy|object
49
     */
50 242
    final public function create($attributes = []): Proxy
51
    {
52 242
        $proxy = new Proxy($this->instantiate($attributes));
53
54 238
        if (!$this->persist) {
55 56
            return $proxy;
56
        }
57
58 182
        $proxy->save()->disableAutoRefresh();
59
60 182
        foreach ($this->afterPersist as $callback) {
61 4
            $proxy->executeCallback($callback, $attributes);
62
        }
63
64 182
        return $proxy->enableAutoRefresh();
65
    }
66
67
    /**
68
     * @param array|callable $attributes
69
     *
70
     * @return Proxy[]|object[]
71
     */
72 60
    final public function createMany(int $number, $attributes = []): array
73
    {
74 60
        return \array_map(
75
            function() use ($attributes) {
76 60
                return $this->create($attributes);
77 60
            },
78 60
            \array_fill(0, $number, null)
79
        );
80
    }
81
82 64
    public function withoutPersisting(): self
83
    {
84 64
        $cloned = clone $this;
85 64
        $cloned->persist = false;
86
87 64
        return $cloned;
88
    }
89
90
    /**
91
     * @param array|callable $attributes
92
     */
93 190
    final public function withAttributes($attributes = []): self
94
    {
95 190
        $cloned = clone $this;
96 190
        $cloned->attributeSet[] = $attributes;
97
98 190
        return $cloned;
99
    }
100
101
    /**
102
     * @param callable $callback (array $attributes): array
103
     */
104 12
    final public function beforeInstantiate(callable $callback): self
105
    {
106 12
        $cloned = clone $this;
107 12
        $cloned->beforeInstantiate[] = $callback;
108
109 12
        return $cloned;
110
    }
111
112
    /**
113
     * @param callable $callback (object $object, array $attributes): void
114
     */
115 8
    final public function afterInstantiate(callable $callback): self
116
    {
117 8
        $cloned = clone $this;
118 8
        $cloned->afterInstantiate[] = $callback;
119
120 8
        return $cloned;
121
    }
122
123
    /**
124
     * @param callable $callback (object $object, array $attributes, ObjectManager $objectManager): void
125
     */
126 8
    final public function afterPersist(callable $callback): self
127
    {
128 8
        $cloned = clone $this;
129 8
        $cloned->afterPersist[] = $callback;
130
131 8
        return $cloned;
132
    }
133
134
    /**
135
     * @param callable $instantiator (array $attributes, string $class): object
136
     */
137 8
    final public function instantiateWith(callable $instantiator): self
138
    {
139 8
        $cloned = clone $this;
140 8
        $cloned->instantiator = $instantiator;
141
142 8
        return $cloned;
143
    }
144
145
    /**
146
     * @internal
147
     */
148 278
    final public static function boot(Configuration $configuration): void
149
    {
150 278
        self::$configuration = $configuration;
151 278
    }
152
153
    /**
154
     * @internal
155
     */
156 250
    final public static function configuration(): Configuration
157
    {
158 250
        if (!self::$configuration) {
159
            throw new \RuntimeException('Foundry is not yet booted, is the ZenstruckFoundryBundle installed/configured?');
160
        }
161
162 250
        return self::$configuration;
163
    }
164
165 194
    final public static function faker(): Faker\Generator
166
    {
167 194
        return self::configuration()->faker();
168
    }
169
170
    /**
171
     * @param array|callable $attributes
172
     */
173 242
    private static function normalizeAttributes($attributes): array
174
    {
175 242
        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...
176
    }
177
178
    /**
179
     * @param array|callable $attributes
180
     */
181 242
    private function instantiate($attributes): object
182
    {
183
        // merge the factory attribute set with the passed attributes
184 242
        $attributeSet = \array_merge($this->attributeSet, [$attributes]);
185
186
        // normalize each attribute set and collapse
187 242
        $attributes = \array_merge(...\array_map([$this, 'normalizeAttributes'], $attributeSet));
188
189 242
        foreach ($this->beforeInstantiate as $callback) {
190 8
            $attributes = $callback($attributes);
191
192 8
            if (!\is_array($attributes)) {
193 4
                throw new \LogicException('Before Instantiate event callback must return an array.');
194
            }
195
        }
196
197
        // filter each attribute to convert proxies and factories to objects
198 238
        $attributes = \array_map(
199
            function($value) {
200 226
                return $this->normalizeAttribute($value);
201 238
            },
202 238
            $attributes
203
        );
204
205
        // instantiate the object with the users instantiator or if not set, the default instantiator
206 238
        $object = ($this->instantiator ?? self::configuration()->instantiator())($attributes, $this->class);
207
208 238
        foreach ($this->afterInstantiate as $callback) {
209 4
            $callback($object, $attributes);
210
        }
211
212 238
        return $object;
213
    }
214
215
    /**
216
     * @param mixed $value
217
     *
218
     * @return mixed
219
     */
220 226
    private function normalizeAttribute($value)
221
    {
222 226
        if ($value instanceof Proxy) {
223 24
            return $value->object();
224
        }
225
226 226
        if (\is_array($value)) {
227
            // possible OneToMany/ManyToMany relationship
228 12
            return \array_map(
229
                function($value) {
230 12
                    return $this->normalizeAttribute($value);
231 12
                },
232 12
                $value
233
            );
234
        }
235
236 226
        if (!$value instanceof self) {
237 226
            return $value;
238
        }
239
240 24
        if (!$this->persist) {
241
            // ensure attribute Factory's are also not persisted
242 4
            $value = $value->withoutPersisting();
243
        }
244
245 24
        return $value->create()->object();
246
    }
247
}
248