Issues (131)

src/ModelFactory.php (8 issues)

1
<?php
2
3
namespace Zenstruck\Foundry;
4
5
/**
6
 * @template TModel of object
7
 * @template-extends Factory<TModel>
8
 *
9
 * @method static Proxy[]|TModel[] createMany(int $number, array|callable $attributes = [])
10
 * @psalm-method static list<Proxy<TModel>> createMany(int $number, array|callable $attributes = [])
11
 *
12
 * @author Kevin Bond <[email protected]>
13 604
 */
14
abstract class ModelFactory extends Factory
15 604
{
16 604
    public function __construct()
17
    {
18
        parent::__construct(static::getClass());
19
    }
20
21
    public static function __callStatic(string $name, array $arguments)
22
    {
23
        if ('createMany' !== $name) {
24 611
            throw new \BadMethodCallException(\sprintf('Call to undefined static method "%s::%s".', static::class, $name));
25
        }
26 611
27 10
        return static::new()->many($arguments[0])->create($arguments[1] ?? []);
28 10
    }
29
30
    /**
31
     * @param array|callable|string $defaultAttributes If string, assumes state
32 611
     * @param string                ...$states         Optionally pass default states (these must be methods on your ObjectFactory with no arguments)
33 16
     *
34 10
     * @return static
35
     */
36
    final public static function new($defaultAttributes = [], string ...$states): self
37
    {
38 604
        if (\is_string($defaultAttributes)) {
39 604
            $states = \array_merge([$defaultAttributes], $states);
40 604
            $defaultAttributes = [];
41
        }
42
43 604
        try {
44 20
            $factory = self::isBooted() ? self::configuration()->factories()->create(static::class) : new static();
45
        } catch (\ArgumentCountError $e) {
46
            throw new \RuntimeException('Model Factories with dependencies (Model Factory services) cannot be created before foundry is booted.', 0, $e);
47 600
        }
48 10
49
        $factory = $factory
50
            ->withAttributes([$factory, 'getDefaults'])
51 600
            ->withAttributes($defaultAttributes)
52
            ->initialize()
53
        ;
54
55
        if (!$factory instanceof static) {
0 ignored issues
show
$factory is always a sub-type of static.
Loading history...
56
            throw new \TypeError(\sprintf('"%1$s::initialize()" must return an instance of "%1$s".', static::class));
57
        }
58
59
        foreach ($states as $state) {
60
            $factory = $factory->{$state}();
61
        }
62 10
63
        return $factory;
64 10
    }
65 10
66
    /**
67
     * A shortcut to create a single model without states.
68 10
     *
69
     * @return Proxy<TModel>&TModel
70
     * @psalm-return Proxy<TModel>
71
     */
72
    final public static function createOne(array $attributes = []): Proxy
73
    {
74 20
        return static::new()->create($attributes);
75
    }
76 20
77
    /**
78
     * Try and find existing object for the given $attributes. If not found,
79
     * instantiate and persist.
80
     *
81
     * @return Proxy<TModel>&TModel
82 10
     * @psalm-return Proxy<TModel>
83
     */
84 10
    final public static function findOrCreate(array $attributes): Proxy
85
    {
86
        if ($found = static::repository()->find($attributes)) {
87
            return \is_array($found) ? $found[0] : $found;
0 ignored issues
show
The condition is_array($found) is always false.
Loading history...
88
        }
89
90 10
        return static::new()->create($attributes);
91
    }
92 10
93
    /**
94
     * @see RepositoryProxy::first()
95
     *
96 210
     * @return Proxy<TModel>&TModel
97
     * @psalm-return Proxy<TModel>
98 210
     *
99
     * @throws \RuntimeException If no entities exist
100
     */
101
    final public static function first(string $sortedField = 'id'): Proxy
102
    {
103
        if (null === $proxy = static::repository()->first($sortedField)) {
104
            throw new \RuntimeException(\sprintf('No "%s" objects persisted.', static::getClass()));
105
        }
106 600
107
        return $proxy;
108 600
    }
109
110
    /**
111
     * @see RepositoryProxy::last()
112
     *
113
     * @return Proxy<TModel>&TModel
114
     * @psalm-return Proxy<TModel>
115
     *
116 30
     * @throws \RuntimeException If no entities exist
117
     */
118 30
    final public static function last(string $sortedField = 'id'): Proxy
119
    {
120
        if (null === $proxy = static::repository()->last($sortedField)) {
121
            throw new \RuntimeException(\sprintf('No "%s" objects persisted.', static::getClass()));
122
        }
123
124
        return $proxy;
125
    }
126
127
    /**
128
     * @see RepositoryProxy::random()
129
     *
130
     * @return Proxy<TModel>&TModel
131
     * @psalm-return Proxy<TModel>
132
     */
133
    final public static function random(array $attributes = []): Proxy
134
    {
135
        return static::repository()->random($attributes);
136
    }
137
138
    /**
139
     * Fetch one random object and create a new object if none exists.
140
     *
141
     * @return Proxy<TModel>&TModel
142
     * @psalm-return Proxy<TModel>
143
     */
144
    final public static function randomOrCreate(array $attributes = []): Proxy
145
    {
146
        try {
147
            return static::repository()->random($attributes);
148
        } catch (\RuntimeException $e) {
149
            return static::new()->create($attributes);
150
        }
151
    }
152
153
    /**
154
     * @see RepositoryProxy::randomSet()
155
     *
156
     * @return list<TModel&Proxy<TModel>>
0 ignored issues
show
The type Zenstruck\Foundry\list was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
157
     * @psalm-return list<Proxy<TModel>>
158
     */
159
    final public static function randomSet(int $number, array $attributes = []): array
160
    {
161
        return static::repository()->randomSet($number, $attributes);
0 ignored issues
show
Bug Best Practice introduced by
The expression return static::repositor...t($number, $attributes) returns the type array which is incompatible with the documented return type Zenstruck\Foundry\list.
Loading history...
162
    }
163
164
    /**
165
     * @see RepositoryProxy::randomRange()
166
     *
167
     * @return list<TModel&Proxy<TModel>>
168
     * @psalm-return list<Proxy<TModel>>
169
     */
170
    final public static function randomRange(int $min, int $max, array $attributes = []): array
171
    {
172
        return static::repository()->randomRange($min, $max, $attributes);
0 ignored issues
show
Bug Best Practice introduced by
The expression return static::repositor...min, $max, $attributes) returns the type array which is incompatible with the documented return type Zenstruck\Foundry\list.
Loading history...
173
    }
174
175
    /**
176
     * @see RepositoryProxy::count()
177
     */
178
    final public static function count(): int
179
    {
180
        return static::repository()->count();
181
    }
182
183
    /**
184
     * @see RepositoryProxy::truncate()
185
     */
186
    final public static function truncate(): void
187
    {
188
        static::repository()->truncate();
189
    }
190
191
    /**
192
     * @see RepositoryProxy::findAll()
193
     *
194
     * @return list<TModel&Proxy<TModel>>
195
     * @psalm-return list<Proxy<TModel>>
196
     */
197
    final public static function all(): array
198
    {
199
        return static::repository()->findAll();
0 ignored issues
show
Bug Best Practice introduced by
The expression return static::repository()->findAll() returns the type array which is incompatible with the documented return type Zenstruck\Foundry\list.
Loading history...
200
    }
201
202
    /**
203
     * @see RepositoryProxy::find()
204
     *
205
     * @return Proxy<TModel>&TModel
206
     * @psalm-return Proxy<TModel>
207
     *
208
     * @throws \RuntimeException If no entity found
209
     */
210
    final public static function find($criteria): Proxy
211
    {
212
        if (null === $proxy = static::repository()->find($criteria)) {
0 ignored issues
show
The condition null === $proxy = static...tory()->find($criteria) is always false.
Loading history...
213
            throw new \RuntimeException(\sprintf('Could not find "%s" object.', static::getClass()));
214
        }
215
216
        return $proxy;
217
    }
218
219
    /**
220
     * @see RepositoryProxy::findBy()
221
     *
222
     * @return list<TModel&Proxy<TModel>>
223
     * @psalm-return list<Proxy<TModel>>
224
     */
225
    final public static function findBy(array $attributes): array
226
    {
227
        return static::repository()->findBy($attributes);
0 ignored issues
show
Bug Best Practice introduced by
The expression return static::repository()->findBy($attributes) returns the type array which is incompatible with the documented return type Zenstruck\Foundry\list.
Loading history...
228
    }
229
230
    final public static function assert(): RepositoryAssertions
231
    {
232
        return static::repository()->assert();
233
    }
234
235
    /**
236
     * @psalm-return RepositoryProxy<TModel>
237
     */
238
    final public static function repository(): RepositoryProxy
239
    {
240
        return static::configuration()->repositoryFor(static::getClass());
241
    }
242
243
    /**
244
     * @internal
245
     * @psalm-return class-string<TModel>
246
     */
247
    final public static function getEntityClass(): string
248
    {
249
        return static::getClass();
250
    }
251
252
    /** @psalm-return class-string<TModel> */
253
    abstract protected static function getClass(): string;
254
255
    /**
256
     * Override to add default instantiator and default afterInstantiate/afterPersist events.
257
     *
258
     * @return static
259
     */
260
    protected function initialize()
261
    {
262
        return $this;
263
    }
264
265
    /**
266
     * @param array|callable $attributes
267
     *
268
     * @return static
269
     */
270
    final protected function addState($attributes = []): self
271
    {
272
        return $this->withAttributes($attributes);
273
    }
274
275
    abstract protected function getDefaults(): array;
276
}
277