Passed
Push — master ( f054e3...118186 )
by Kevin
03:05
created

Factory::withAttributes()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 3
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 6
ccs 4
cts 4
cp 1
crap 1
rs 10
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
        // merge the factory attribute set with the passed attributes
53 262
        $attributeSet = \array_merge($this->attributeSet, [$attributes]);
54
55
        // normalize each attribute set and collapse
56 262
        $attributes = \array_merge(...\array_map([$this, 'normalizeAttributes'], $attributeSet));
57
58 262
        foreach ($this->beforeInstantiate as $callback) {
59 8
            $attributes = $callback($attributes);
60
61 8
            if (!\is_array($attributes)) {
62 4
                throw new \LogicException('Before Instantiate event callback must return an array.');
63
            }
64
        }
65
66
        // filter each attribute to convert proxies and factories to objects
67 258
        $attributes = \array_map(
68
            function($value) {
69 246
                return $this->normalizeAttribute($value);
70 258
            },
71 258
            $attributes
72
        );
73
74
        // instantiate the object with the users instantiator or if not set, the default instantiator
75 258
        $object = ($this->instantiator ?? self::configuration()->instantiator())($attributes, $this->class);
76
77 258
        foreach ($this->afterInstantiate as $callback) {
78 4
            $callback($object, $attributes);
79
        }
80
81 258
        $proxy = new Proxy($object);
82
83 258
        if (!$this->persist) {
84 56
            return $proxy;
85
        }
86
87
        return $proxy->save()->withoutAutoRefresh(function(Proxy $proxy) use ($attributes) {
88 202
            foreach ($this->afterPersist as $callback) {
89 4
                $proxy->executeCallback($callback, $attributes);
90
            }
91 202
        });
92
    }
93
94
    /**
95
     * @param array|callable $attributes
96
     *
97
     * @return Proxy[]|object[]
98
     */
99 60
    final public function createMany(int $number, $attributes = []): array
100
    {
101 60
        return \array_map(
102
            function() use ($attributes) {
103 60
                return $this->create($attributes);
104 60
            },
105 60
            \array_fill(0, $number, null)
106
        );
107
    }
108
109 64
    public function withoutPersisting(): self
110
    {
111 64
        $cloned = clone $this;
112 64
        $cloned->persist = false;
113
114 64
        return $cloned;
115
    }
116
117
    /**
118
     * @param array|callable $attributes
119
     */
120 210
    final public function withAttributes($attributes = []): self
121
    {
122 210
        $cloned = clone $this;
123 210
        $cloned->attributeSet[] = $attributes;
124
125 210
        return $cloned;
126
    }
127
128
    /**
129
     * @param callable $callback (array $attributes): array
130
     */
131 12
    final public function beforeInstantiate(callable $callback): self
132
    {
133 12
        $cloned = clone $this;
134 12
        $cloned->beforeInstantiate[] = $callback;
135
136 12
        return $cloned;
137
    }
138
139
    /**
140
     * @param callable $callback (object $object, array $attributes): void
141
     */
142 8
    final public function afterInstantiate(callable $callback): self
143
    {
144 8
        $cloned = clone $this;
145 8
        $cloned->afterInstantiate[] = $callback;
146
147 8
        return $cloned;
148
    }
149
150
    /**
151
     * @param callable $callback (object|Proxy $object, array $attributes): void
152
     */
153 8
    final public function afterPersist(callable $callback): self
154
    {
155 8
        $cloned = clone $this;
156 8
        $cloned->afterPersist[] = $callback;
157
158 8
        return $cloned;
159
    }
160
161
    /**
162
     * @param callable $instantiator (array $attributes, string $class): object
163
     */
164 8
    final public function instantiateWith(callable $instantiator): self
165
    {
166 8
        $cloned = clone $this;
167 8
        $cloned->instantiator = $instantiator;
168
169 8
        return $cloned;
170
    }
171
172
    /**
173
     * @internal
174
     */
175 300
    final public static function boot(Configuration $configuration): void
176
    {
177 300
        self::$configuration = $configuration;
178 300
    }
179
180
    /**
181
     * @internal
182
     */
183 290
    final public static function configuration(): Configuration
184
    {
185 290
        if (!self::isBooted()) {
186
            throw new \RuntimeException('Foundry is not yet booted. Using in a test: is your Test case using the Factories trait? Using in a fixture: is ZenstruckFoundryBundle enabled for this environment?');
187
        }
188
189 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...
190
    }
191
192
    /**
193
     * @internal
194
     */
195 316
    final public static function isBooted(): bool
196
    {
197 316
        return null !== self::$configuration;
198
    }
199
200 214
    final public static function faker(): Faker\Generator
201
    {
202 214
        return self::configuration()->faker();
203
    }
204
205
    /**
206
     * @param array|callable $attributes
207
     */
208 262
    private static function normalizeAttributes($attributes): array
209
    {
210 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...
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