├── .gitignore
├── .gitattributes
├── src
├── CrossJoinSequence.php
├── Sequence.php
├── HasFactory.php
├── Data.php
└── Factory.php
├── phpunit.xml
├── .github
└── workflows
│ ├── run-cs-fix.yml
│ └── run-tests.yml
├── composer.json
├── license.md
└── readme.md
/.gitignore:
--------------------------------------------------------------------------------
1 | /vendor/
2 | .composer
3 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | art/ export-ignore
2 | tests/ export-ignore
--------------------------------------------------------------------------------
/src/CrossJoinSequence.php:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 | ./tests
10 |
11 |
12 |
13 |
14 | ./src
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/.github/workflows/run-cs-fix.yml:
--------------------------------------------------------------------------------
1 | name: run-cs-fix
2 |
3 | on:
4 | push:
5 |
6 | jobs:
7 | lint:
8 | runs-on: ubuntu-latest
9 | strategy:
10 | fail-fast: true
11 | matrix:
12 | php: [8.3]
13 |
14 | steps:
15 | - name: Checkout code
16 | uses: actions/checkout@v4
17 |
18 | - name: Setup PHP
19 | uses: shivammathur/setup-php@v2
20 | with:
21 | php-version: ${{ matrix.php }}
22 | extensions: json, dom, curl, libxml, mbstring
23 | coverage: none
24 |
25 | - name: Install Pint
26 | run: composer global require laravel/pint
27 |
28 | - name: Run Pint
29 | run: pint
30 |
31 | - name: Commit linted files
32 | uses: stefanzweifel/git-auto-commit-action@v5
33 | with:
34 | commit_message: "Fix code style"
35 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "directorytree/dummy",
3 | "type": "library",
4 | "license": "MIT",
5 | "archive": {
6 | "exclude": [
7 | "/tests"
8 | ]
9 | },
10 | "autoload": {
11 | "psr-4": {
12 | "DirectoryTree\\Dummy\\": "src/"
13 | }
14 | },
15 | "autoload-dev": {
16 | "psr-4": {
17 | "DirectoryTree\\Dummy\\Tests\\": "tests/"
18 | }
19 | },
20 | "authors": [
21 | {
22 | "name": "Steve Bauman",
23 | "email": "steven_bauman@outlook.com"
24 | }
25 | ],
26 | "required": {
27 | "fakerphp/faker": "^1.0",
28 | "illuminate/collections": "^9.0|^10.0|^11.0|^12.0"
29 | },
30 | "require-dev": {
31 | "laravel/pint": "^1.0",
32 | "pestphp/pest": "^1.0|^2.0|^3.0",
33 | "orchestra/testbench": "^7.0|^8.0|^9.0|^10.0"
34 | },
35 | "config": {
36 | "allow-plugins": {
37 | "pestphp/pest-plugin": true
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/license.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 Steve Bauman
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/Sequence.php:
--------------------------------------------------------------------------------
1 | sequence = $sequence;
30 | $this->count = count($sequence);
31 | }
32 |
33 | /**
34 | * Get the current count of the sequence items.
35 | */
36 | public function count(): int
37 | {
38 | return $this->count;
39 | }
40 |
41 | /**
42 | * Get the next value in the sequence.
43 | */
44 | public function __invoke(): mixed
45 | {
46 | return tap(value($this->sequence[$this->index % $this->count], $this), function () {
47 | $this->index = $this->index + 1;
48 | });
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/HasFactory.php:
--------------------------------------------------------------------------------
1 | using(
15 | function (Generator $faker, array $attributes) {
16 | return static::toFactoryInstance(
17 | array_merge(static::getFactoryDefinition($faker, $attributes), $attributes)
18 | );
19 | }
20 | );
21 | }
22 |
23 | /**
24 | * Get a new factory instance.
25 | */
26 | protected static function newFactory(array $attributes): Factory
27 | {
28 | return (new Factory(class: static::class))->state($attributes);
29 | }
30 |
31 | /**
32 | * Transform the dummy data into a class instance.
33 | */
34 | abstract protected static function toFactoryInstance(array $attributes): mixed;
35 |
36 | /**
37 | * Define the dummy data definition.
38 | */
39 | abstract protected static function getFactoryDefinition(Generator $faker): array;
40 | }
41 |
--------------------------------------------------------------------------------
/.github/workflows/run-tests.yml:
--------------------------------------------------------------------------------
1 | name: run-tests
2 |
3 | on:
4 | push:
5 | pull_request:
6 | schedule:
7 | - cron: "0 0 * * *"
8 |
9 | jobs:
10 | run-tests:
11 | runs-on: ${{ matrix.os }}
12 | strategy:
13 | fail-fast: false
14 | matrix:
15 | os: [ubuntu-latest]
16 | php: [8.4, 8.3, 8.2]
17 | include:
18 | - php: 8.4
19 | laravel: 12.*
20 | testbench: 10.*
21 |
22 | - php: 8.3
23 | laravel: 11.*
24 | testbench: 9.*
25 |
26 | - php: 8.2
27 | laravel: 10.*
28 | testbench: 8.*
29 |
30 | name: ${{ matrix.os }} - P${{ matrix.php }} - L${{ matrix.laravel }}
31 |
32 | steps:
33 | - name: Checkout code
34 | uses: actions/checkout@v4
35 |
36 | - name: Cache dependencies
37 | uses: actions/cache@v4
38 | with:
39 | path: ~/.composer/cache/files
40 | key: dependencies-laravel-${{ matrix.laravel }}-php-${{ matrix.php }}-composer-${{ hashFiles('composer.json') }}
41 |
42 | - name: Setup PHP
43 | uses: shivammathur/setup-php@v2
44 | with:
45 | php-version: ${{ matrix.php }}
46 |
47 | - name: Install dependencies
48 | run: |
49 | composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" --no-interaction --dev
50 | composer update --prefer-dist --no-interaction
51 |
52 | - name: Execute tests
53 | run: vendor/bin/pest
54 |
--------------------------------------------------------------------------------
/src/Data.php:
--------------------------------------------------------------------------------
1 | $value) {
22 | $this->attributes[$key] = $value;
23 | }
24 | }
25 |
26 | /**
27 | * Set an attribute on the data instance using "dot" notation.
28 | */
29 | public function set(string $key, mixed $value): static
30 | {
31 | Arr::set($this->attributes, $key, $value);
32 |
33 | return $this;
34 | }
35 |
36 | /**
37 | * Get an attribute from the data instance using "dot" notation.
38 | */
39 | public function get(string $key, mixed $default = null): mixed
40 | {
41 | return Arr::get($this->attributes, $key, $default);
42 | }
43 |
44 | /**
45 | * Get an attribute from the data instance.
46 | */
47 | public function value(string $key, mixed $default = null): mixed
48 | {
49 | if (array_key_exists($key, $this->attributes)) {
50 | return $this->attributes[$key];
51 | }
52 |
53 | return value($default);
54 | }
55 |
56 | /**
57 | * Get the value of the given key as a new data instance.
58 | */
59 | public function scope(string $key, mixed $default = null): static
60 | {
61 | return new static(
62 | (array) $this->get($key, $default)
63 | );
64 | }
65 |
66 | /**
67 | * Get the attributes from the data instance.
68 | *
69 | * @param array|mixed|null $keys
70 | */
71 | public function all(mixed $keys = null): array
72 | {
73 | $data = $this->attributes;
74 |
75 | if (! $keys) {
76 | return $data;
77 | }
78 |
79 | $results = [];
80 |
81 | foreach (is_array($keys) ? $keys : func_get_args() as $key) {
82 | Arr::set($results, $key, Arr::get($data, $key));
83 | }
84 |
85 | return $results;
86 | }
87 |
88 | /**
89 | * Convert the object into something JSON serializable.
90 | */
91 | public function jsonSerialize(): array
92 | {
93 | return $this->attributes;
94 | }
95 |
96 | /**
97 | * Determine if the given offset exists.
98 | */
99 | public function offsetExists(mixed $offset): bool
100 | {
101 | return isset($this->attributes[$offset]);
102 | }
103 |
104 | /**
105 | * Get the value for a given offset.
106 | */
107 | public function offsetGet(mixed $offset): mixed
108 | {
109 | return $this->value($offset);
110 | }
111 |
112 | /**
113 | * Set the value at the given offset.
114 | */
115 | public function offsetSet(mixed $offset, mixed $value): void
116 | {
117 | $this->attributes[$offset] = $value;
118 | }
119 |
120 | /**
121 | * Unset the value at the given offset.
122 | */
123 | public function offsetUnset(mixed $offset): void
124 | {
125 | unset($this->attributes[$offset]);
126 | }
127 |
128 | /**
129 | * Handle dynamic calls to the data instance to set attributes.
130 | */
131 | public function __call(string $method, array $parameters): static
132 | {
133 | $this->attributes[$method] = count($parameters) > 0 ? reset($parameters) : true;
134 |
135 | return $this;
136 | }
137 |
138 | /**
139 | * Dynamically retrieve the value of an attribute.
140 | */
141 | public function __get(string $key): mixed
142 | {
143 | return $this->value($key);
144 | }
145 |
146 | /**
147 | * Dynamically set the value of an attribute.
148 | */
149 | public function __set(string $key, mixed $value): void
150 | {
151 | $this->offsetSet($key, $value);
152 | }
153 |
154 | /**
155 | * Dynamically check if an attribute is set.
156 | */
157 | public function __isset(string $key): bool
158 | {
159 | return $this->offsetExists($key);
160 | }
161 |
162 | /**
163 | * Dynamically unset an attribute.
164 | */
165 | public function __unset(string $key): void
166 | {
167 | $this->offsetUnset($key);
168 | }
169 | }
170 |
--------------------------------------------------------------------------------
/src/Factory.php:
--------------------------------------------------------------------------------
1 | faker = $this->newFaker();
33 | }
34 |
35 | /**
36 | * Get a new dummy factory instance for the given attributes.
37 | */
38 | public static function new(Closure|array $attributes = []): static
39 | {
40 | return (new static)->state($attributes)->configure();
41 | }
42 |
43 | /**
44 | * Get a new dummy factory instance for the given number of classes.
45 | */
46 | public static function times(int $count): static
47 | {
48 | return static::new()->count($count);
49 | }
50 |
51 | /**
52 | * Define the dummy class' default state.
53 | */
54 | protected function definition(): array
55 | {
56 | return [];
57 | }
58 |
59 | /**
60 | * Generate a new dummy class instance.
61 | */
62 | protected function generate(array $attributes): mixed
63 | {
64 | return new Data($attributes);
65 | }
66 |
67 | /**
68 | * Configure the dummy factory.
69 | */
70 | protected function configure(): static
71 | {
72 | return $this;
73 | }
74 |
75 | /**
76 | * Create a new collection of dummy instances.
77 | */
78 | protected function collect(array $instances = []): mixed
79 | {
80 | return new Collection($instances);
81 | }
82 |
83 | /**
84 | * Get the raw attributes generated by the factory.
85 | */
86 | public function raw(Closure|array $attributes = []): array
87 | {
88 | if ($this->count === null) {
89 | return $this->state($attributes)->getExpandedAttributes();
90 | }
91 |
92 | return array_map(function () use ($attributes) {
93 | return $this->state($attributes)->getExpandedAttributes();
94 | }, range(1, $this->count));
95 | }
96 |
97 | /**
98 | * Create a collection of classes.
99 | */
100 | public function make(Closure|array $attributes = []): mixed
101 | {
102 | if (! empty($attributes)) {
103 | return $this->state($attributes)->make();
104 | }
105 |
106 | if ($this->count === null) {
107 | return tap($this->makeInstance(), function ($instance) {
108 | $this->callAfterMaking($this->collect([$instance]));
109 | });
110 | }
111 |
112 | if ($this->count < 1) {
113 | return $this->collect();
114 | }
115 |
116 | $instances = $this->collect(array_map(function () {
117 | return $this->makeInstance();
118 | }, range(1, $this->count)));
119 |
120 | $this->callAfterMaking($instances);
121 |
122 | return $instances;
123 | }
124 |
125 | /**
126 | * Make an instance of the class with the given attributes.
127 | */
128 | protected function makeInstance(): mixed
129 | {
130 | $attributes = $this->getExpandedAttributes();
131 |
132 | if ($this->using) {
133 | return ($this->using)($this->faker, $attributes);
134 | }
135 |
136 | return $this->generate($attributes);
137 | }
138 |
139 | /**
140 | * Get a raw attributes array for the class.
141 | */
142 | protected function getExpandedAttributes(): array
143 | {
144 | return $this->expandAttributes($this->getRawAttributes());
145 | }
146 |
147 | /**
148 | * Get the raw attributes for the class as an array.
149 | */
150 | protected function getRawAttributes(): array
151 | {
152 | return $this->states->pipe(function ($states) {
153 | return $states;
154 | })->reduce(function ($carry, $state) {
155 | if ($state instanceof Closure) {
156 | $state = $state->bindTo($this);
157 | }
158 |
159 | return array_merge($carry, $state($carry));
160 | }, $this->definition());
161 | }
162 |
163 | /**
164 | * Expand all attributes to their underlying values.
165 | */
166 | protected function expandAttributes(array $definition): array
167 | {
168 | return (new Collection($definition))->map(function ($attribute, $key) use (&$definition) {
169 | if (is_callable($attribute) && ! is_string($attribute) && ! is_array($attribute)) {
170 | $attribute = $attribute($definition);
171 | }
172 |
173 | $definition[$key] = $attribute;
174 |
175 | return $attribute;
176 | })->all();
177 | }
178 |
179 | /**
180 | * Add a new state transformation to the class definition.
181 | */
182 | public function state(mixed $state): static
183 | {
184 | return $this->newInstance([
185 | 'states' => $this->states->concat([
186 | is_callable($state) ? $state : function () use ($state) {
187 | return $state;
188 | },
189 | ]),
190 | ]);
191 | }
192 |
193 | /**
194 | * Set a single class attribute.
195 | */
196 | public function set(string|int $key, mixed $value): static
197 | {
198 | return $this->state([$key => $value]);
199 | }
200 |
201 | /**
202 | * Add a new sequenced state transformation to the class definition.
203 | */
204 | public function sequence(mixed ...$sequence): static
205 | {
206 | return $this->state(new Sequence(...$sequence));
207 | }
208 |
209 | /**
210 | * Add a new sequenced state transformation to the class definition and update the pending creation count to the size of the sequence.
211 | */
212 | public function forEachSequence(array ...$sequence): static
213 | {
214 | return $this->state(new Sequence(...$sequence))->count(count($sequence));
215 | }
216 |
217 | /**
218 | * Add a new cross joined sequenced state transformation to the class definition.
219 | */
220 | public function crossJoinSequence(array ...$sequence): static
221 | {
222 | return $this->state(new CrossJoinSequence(...$sequence));
223 | }
224 |
225 | /**
226 | * Set the callback to be used to create the class instance.
227 | */
228 | public function using(Closure $callback): static
229 | {
230 | return $this->newInstance([
231 | 'using' => $callback,
232 | ]);
233 | }
234 |
235 | /**
236 | * Add a new "after making" callback to the class definition.
237 | */
238 | public function afterMaking(Closure $callback): static
239 | {
240 | return $this->newInstance([
241 | 'afterMaking' => $this->afterMaking->concat([$callback]),
242 | ]);
243 | }
244 |
245 | /**
246 | * Call the "after making" callbacks for the given class instances.
247 | */
248 | protected function callAfterMaking(iterable $instances): void
249 | {
250 | foreach ($instances as $instance) {
251 | $this->afterMaking->each(
252 | fn ($callback) => $callback($instance)
253 | );
254 | }
255 | }
256 |
257 | /**
258 | * Specify how many dummy classes should be generated.
259 | */
260 | public function count(?int $count): static
261 | {
262 | return $this->newInstance(['count' => $count]);
263 | }
264 |
265 | /**
266 | * Create a new instance of the factory builder with the given mutated properties.
267 | */
268 | protected function newInstance(array $arguments = []): static
269 | {
270 | return new static(...array_values(array_merge([
271 | 'count' => $this->count,
272 | 'class' => $this->class,
273 | 'using' => $this->using,
274 | 'states' => $this->states,
275 | 'afterMaking' => $this->afterMaking,
276 | ], $arguments)));
277 | }
278 |
279 | /**
280 | * Get the Faker instance.
281 | */
282 | public function faker(): Generator
283 | {
284 | return $this->faker ??= $this->newFaker();
285 | }
286 |
287 | /**
288 | * Get a new Faker instance.
289 | */
290 | protected function newFaker(): Generator
291 | {
292 | if (class_exists(Container::class)) {
293 | return Container::getInstance()->make(Generator::class);
294 | }
295 |
296 | return FakerFactory::create();
297 | }
298 |
299 | /**
300 | * Handle dynamic state method calls.
301 | */
302 | public function __call(string $method, array $parameters): static
303 | {
304 | if (! $this->class) {
305 | throw new BadMethodCallException('Cannot call state methods on a factory without a using class.');
306 | }
307 |
308 | if (! method_exists($this->class, $stateMethod = $this->getStateMethod($method))) {
309 | throw new BadMethodCallException("Method [{$method}] does not exist on [{$this->class}].");
310 | }
311 |
312 | if (! is_callable([$this->class, $stateMethod])) {
313 | throw new BadMethodCallException("Method [{$method}] is not callable on [{$this->class}].");
314 | }
315 |
316 | return $this->state(
317 | call_user_func([$this->class, $stateMethod], $parameters)
318 | );
319 | }
320 |
321 | /**
322 | * Get the state method name for the given method name.
323 | */
324 | protected function getStateMethod(string $method): string
325 | {
326 | return 'get'.ucfirst($method).'State';
327 | }
328 | }
329 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Generate PHP class instances populated with fake dummy data using Faker
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | ---
17 |
18 | ## Index
19 |
20 | - [Requirements](#requirements)
21 | - [Installation](#installation)
22 | - [Introduction](#introduction)
23 | - [Setup](#setup)
24 | - [HasFactory Trait](#hasfactory-trait)
25 | - [Class Factory](#class-factory)
26 | - [Usage](#usage)
27 | - [Factory States](#factory-states)
28 | - [Factory Callbacks](#factory-callbacks)
29 | - [Factory Sequences](#factory-sequences)
30 | - [Factory Collections](#factory-collections)
31 |
32 | ## Requirements
33 |
34 | - PHP >= 8.0
35 |
36 | ## Installation
37 |
38 | You can install the package via composer:
39 |
40 | ```bash
41 | composer require directorytree/dummy --dev
42 | ```
43 |
44 | ## Introduction
45 |
46 | Consider you have a class representing a restaurant reservation:
47 |
48 | ```php
49 | namespace App\Data;
50 |
51 | class Reservation
52 | {
53 | public function __construct(
54 | public string $name,
55 | public string $email,
56 | public DateTime $date,
57 | ) {}
58 | }
59 | ```
60 |
61 | To make dummy instances of this class during testing, you have to manually populate it with dummy data.
62 |
63 | This can quickly get out of hand as your class grows, and you may find yourself writing the same dummy data generation code over and over again.
64 |
65 | Dummy provides you with a simple way to generate dummy instances of your classes using a simple API:
66 |
67 | ```php
68 | // Generate one instance:
69 | $reservation = Reservation::factory()->make();
70 |
71 | // Generate multiple instances:
72 | $collection = Reservation::factory()->count(5)->make();
73 | ```
74 |
75 | ## Setup
76 |
77 | Dummy provides you two different ways to generate classes with dummy data.
78 |
79 | ### HasFactory Trait
80 |
81 | The `HasFactory` trait is applied directly to the class you would like to generate dummy instances of.
82 |
83 | To use the `HasFactory` trait, you must implement the `toFactoryInstance` and `getFactoryDefinition` methods:
84 |
85 | ```php
86 | namespace App\Data;
87 |
88 | use DateTime;
89 | use Faker\Generator;
90 | use DirectoryTree\Dummy\HasFactory;
91 |
92 | class Reservation
93 | {
94 | use HasFactory;
95 |
96 | /**
97 | * Constructor.
98 | */
99 | public function __construct(
100 | public string $name,
101 | public string $email,
102 | public DateTime $date,
103 | ) {}
104 |
105 | /**
106 | * Define the factory's default state.
107 | */
108 | protected function getFactoryDefinition(Generator $faker): array
109 | {
110 | return [
111 | 'name' => $faker->name(),
112 | 'email' => $faker->email(),
113 | 'datetime' => $faker->dateTime(),
114 | ];
115 | }
116 |
117 | /**
118 | * Create a new instance of the class using the factory definition.
119 | */
120 | protected static function toFactoryInstance(array $attributes): static
121 | {
122 | return new static(
123 | $attributes['name'],
124 | $attributes['email'],
125 | $attributes['datetime'],
126 | );
127 | }
128 | }
129 | ```
130 |
131 | Once implemented, you may call the `Reservation::factory()` method to create a new dummy factory:
132 |
133 | ```php
134 | $factory = Reservation::factory();
135 | ```
136 |
137 | #### Dynamic State Methods
138 |
139 | The `HasFactory` trait supports defining dynamic state methods. You can define state methods in your class using the format `get{StateName}State` and call them dynamically on the factory:
140 |
141 | ```php
142 | namespace App\Data;
143 |
144 | use DateTime;
145 | use Faker\Generator;
146 | use DirectoryTree\Dummy\HasFactory;
147 |
148 | class Reservation
149 | {
150 | use HasFactory;
151 |
152 | public function __construct(
153 | public string $name,
154 | public string $email,
155 | public DateTime $datetime,
156 | public string $status = 'pending',
157 | public string $type = 'standard',
158 | ) {}
159 |
160 | // Dynamic state methods...
161 |
162 | public static function getConfirmedState(): array
163 | {
164 | return ['status' => 'confirmed'];
165 | }
166 |
167 | public static function getPremiumState(): array
168 | {
169 | return [
170 | 'type' => 'premium',
171 | 'status' => 'confirmed',
172 | ];
173 | }
174 |
175 | public static function getCancelledState(): array
176 | {
177 | return ['status' => 'cancelled'];
178 | }
179 |
180 | protected static function toFactoryInstance(array $attributes): self
181 | {
182 | return new static(
183 | $attributes['name'],
184 | $attributes['email'],
185 | $attributes['datetime'],
186 | $attributes['status'] ?? 'pending',
187 | $attributes['type'] ?? 'standard',
188 | );
189 | }
190 |
191 | protected static function getFactoryDefinition(Generator $faker): array
192 | {
193 | return [
194 | 'name' => $faker->name(),
195 | 'email' => $faker->email(),
196 | 'datetime' => $faker->dateTime(),
197 | ];
198 | }
199 | }
200 | ```
201 |
202 | You can then use these state methods dynamically:
203 |
204 | ```php
205 | // Create a confirmed reservation
206 | $confirmed = Reservation::factory()->confirmed()->make();
207 |
208 | // Create a premium reservation
209 | $premium = Reservation::factory()->premium()->make();
210 |
211 | // Chain multiple states
212 | $premiumCancelled = Reservation::factory()->premium()->cancelled()->make();
213 | ```
214 |
215 | ### Class Factory
216 |
217 | If you need more control over the dummy data generation process, you may use the `Factory` class.
218 |
219 | The `Factory` class is used to generate dummy instances of a class using a separate factory class definition.
220 |
221 | To use the `Factory` class, you must extend it with your own and override the `definition` and `generate` methods:
222 |
223 | ```php
224 | namespace App\Factories;
225 |
226 | use App\Data\Reservation;
227 | use DirectoryTree\Dummy\Factory;
228 |
229 | class ReservationFactory extends Factory
230 | {
231 | /**
232 | * Define the factory's default state.
233 | */
234 | protected function definition(): array
235 | {
236 | return [
237 | 'name' => $this->faker->name(),
238 | 'email' => $this->faker->email(),
239 | 'datetime' => $this->faker->dateTime(),
240 | ];
241 | }
242 |
243 | /**
244 | * Generate a new instance of the class.
245 | */
246 | protected function generate(array $attributes): Reservation
247 | {
248 | return new Reservation(
249 | $attributes['name'],
250 | $attributes['email'],
251 | $attributes['datetime'],
252 | );
253 | }
254 | }
255 | ```
256 |
257 | ## Usage
258 |
259 | Once you've defined a factory, you can generate dummy instances of your class using the `make` method:
260 |
261 | ```php
262 | // Using the trait:
263 | $reservation = Reservation::factory()->make();
264 |
265 | // Using the factory class:
266 | $reservation = ReservationFactory::new()->make();
267 | ```
268 |
269 | To add or override attributes in your definition, you may pass an array of attributes to the `make` method:
270 |
271 | ```php
272 | $reservation = Reservation::factory()->make([
273 | 'name' => 'John Doe',
274 | ]);
275 | ```
276 |
277 | To generate multiple instances of the class, you may use the `count` method:
278 |
279 | > This will return a `Illuminate\SupportCollection` instance containing the generated classes.
280 |
281 | ```php
282 | $collection = Reservation::factory()->count(5)->make();
283 | ```
284 |
285 | ### Factory States
286 |
287 | State manipulation methods allow you to define discrete modifications
288 | that can be applied to your dummy factories in any combination.
289 |
290 | For example, your `App\Factories\Reservation` factory might contain a `tomorrow`
291 | state method that modifies one of its default attribute values:
292 |
293 | ```php
294 | class ReservationFactory extends Factory
295 | {
296 | // ...
297 |
298 | /**
299 | * Indicate that the reservation is for tomorrow.
300 | */
301 | public function tomorrow(): Factory
302 | {
303 | return $this->state(function (array $attributes) {
304 | return ['datetime' => new DateTime('tomorrow')];
305 | });
306 | }
307 | }
308 | ```
309 |
310 | ### Factory Callbacks
311 |
312 | Factory callbacks are registered using the `afterMaking` method and allow you to perform
313 | additional tasks after making or creating a class. You should register these callbacks
314 | by defining a `configure` method on your factory class. This method will be
315 | automatically called when the factory is instantiated:
316 |
317 | ```php
318 | class ReservationFactory extends Factory
319 | {
320 | // ...
321 |
322 | /**
323 | * Configure the dummy factory.
324 | */
325 | protected function configure(): static
326 | {
327 | return $this->afterMaking(function (Reservation $reservation) {
328 | // ...
329 | });
330 | }
331 | }
332 | ```
333 |
334 | ### Factory Sequences
335 |
336 | Sometimes you may wish to alternate the value of a given attribute for each generated
337 | class.
338 |
339 | You may accomplish this by defining a state transformation as a `sequence`:
340 |
341 | ```php
342 | Reservation::factory()
343 | ->count(3)
344 | ->sequence(
345 | ['datetime' => new Datetime('tomorrow')],
346 | ['datetime' => new Datetime('next week')],
347 | ['datetime' => new Datetime('next month')],
348 | )
349 | ->make();
350 | ```
351 |
352 | ### Factory Collections
353 |
354 | By default, when making more than one dummy class, an instance of `Illuminate\Support\Collection` will be returned.
355 |
356 | If you need to customize the collection of classes generated by a factory, you may override the `collect` method:
357 |
358 | ```php
359 | class ReservationFactory extends Factory
360 | {
361 | // ...
362 |
363 | /**
364 | * Create a new collection of classes.
365 | */
366 | public function collect(array $instances = []): ReservationCollection
367 | {
368 | return new ReservationCollection($instances);
369 | }
370 | }
371 | ```
372 |
--------------------------------------------------------------------------------