Cancelled
Pull Request — master (#1)
by Kevin
09:12 queued 01:58
created

Factory::normalizeAttribute()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 21
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 5

Importance

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