├── .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 | --------------------------------------------------------------------------------