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

Factory::create()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 11
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 3

Importance

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