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

Factory   A

Complexity

Total Complexity 30

Size/Duplication

Total Lines 223
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 2
Bugs 0 Features 1
Metric Value
eloc 59
dl 0
loc 223
rs 10
c 2
b 0
f 1
ccs 71
cts 71
cp 1
wmc 30

18 Methods

Rating   Name   Duplication   Size   Complexity  
A proxyByDefault() 0 3 1
A registerDefaultInstantiator() 0 3 1
A afterPersist() 0 6 1
A normalizeAttributes() 0 3 2
A instantiator() 0 6 1
A doInstantiate() 0 27 4
A faker() 0 3 2
A afterInstantiate() 0 6 1
A instantiate() 0 3 1
A __construct() 0 4 1
A beforeInstantiate() 0 6 1
A withAttributes() 0 6 1
A registerFaker() 0 3 1
A instantiateMany() 0 3 1
A defaultInstantiator() 0 3 2
A createMany() 0 3 1
A create() 0 11 3
A filterNormalizedProperty() 0 16 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 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 114
    public function __construct(string $class, $defaultAttributes = [])
38
    {
39 114
        $this->class = $class;
40 114
        $this->attributeSet[] = $defaultAttributes;
41 114
    }
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 82
    final public function create($attributes = [], ?bool $proxy = null): object
67
    {
68 82
        $object = $this->doInstantiate($attributes, true);
69
70 82
        PersistenceManager::persist($object, false);
71
72 82
        foreach ($this->afterPersist as $callback) {
73 2
            $callback($object, $attributes, PersistenceManager::objectManagerFor($object));
74
        }
75
76 82
        return ($proxy ?? self::$proxyByDefault) ? PersistenceManager::proxy($object) : $object;
77
    }
78
79
    /**
80
     * @param array|callable $attributes
81
     *
82
     * @return Proxy[]|object[]
83
     */
84 16
    final public function createMany(int $number, $attributes = [], ?bool $proxy = null): array
85
    {
86 16
        return \array_map(fn() => $this->create($attributes, $proxy), \array_fill(0, $number, null));
87
    }
88
89
    /**
90
     * @param array|callable $attributes
91
     */
92 80
    final public function withAttributes($attributes = []): self
93
    {
94 80
        $cloned = clone $this;
95 80
        $cloned->attributeSet[] = $attributes;
96
97 80
        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 82
    final public static function faker(): Faker\Generator
163
    {
164 82
        return self::$faker ?: self::$faker = Faker\Factory::create();
165
    }
166
167
    /**
168
     * @param array|callable $attributes
169
     */
170 112
    private static function normalizeAttributes($attributes): array
171
    {
172 112
        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 112
    private function doInstantiate($attributes, bool $persisting): object
179
    {
180
        // merge the factory attribute set with the passed attributes
181 112
        $attributeSet = \array_merge($this->attributeSet, [$attributes]);
182
183
        // normalize each attribute set and collapse
184 112
        $attributes = \array_merge(...\array_map([$this, 'normalizeAttributes'], $attributeSet));
185
186 112
        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 110
        $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 110
        $object = ($this->instantiator ?? self::defaultInstantiator())($attributes, $this->class);
199
200 110
        foreach ($this->afterInstantiate as $callback) {
201 2
            $callback($object, $attributes);
202
        }
203
204 110
        return $object;
205
    }
206
207 108
    private static function defaultInstantiator(): callable
208
    {
209 108
        return self::$defaultInstantiator ?: self::$defaultInstantiator = new Instantiator();
210
    }
211
212
    /**
213
     * @param mixed $value
214
     *
215
     * @return mixed
216
     */
217 104
    private static function filterNormalizedProperty($value, bool $persist)
218
    {
219 104
        if ($value instanceof Proxy) {
220 12
            return $value->object();
221
        }
222
223 104
        if (\is_array($value)) {
224
            // possible OneToMany/ManyToMany relationship
225 6
            return \array_map(fn($value) => self::filterNormalizedProperty($value, $persist), $value);
226
        }
227
228 104
        if (!$value instanceof self) {
229 104
            return $value;
230
        }
231
232 12
        return $persist ? $value->create([], false) : $value->instantiate();
233
    }
234
}
235