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

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