Database Seeders
Table of contents
- Overview
- Creating a Factory Class
- Setting Up the Factory Class
- Image Factories
- Using Factories
- A. Instantiating Factories
- B. create()
- C. States
- D. afterCreating
- E. Sequencing
- F. Function Chaining
- g. Attributes
- UserFactory
- Complete Example
1. Overview Table of Contents
We support the user’s ability to utilize factories for seeding data and unit testing purposes. Chappy.php also comes with two factories (UserFactory and ProfileImageFactory).
Factories also support the following features:
- states
- sequences
- afterCreating
Note
⚠ Factories and seeders cannot be executed when APP_ENV=production. Attempting to do so will throw a FactorySeederException.
2. Creating a Factory Class Table of Contents
Create a new factory class by running the following command:
php console make:factory <model-name>
The new file will be created at database\factories.
An example generated factory is shown below:
<?php
namespace Database\Factories;
use App\Models\Contacts;
use Core\Lib\Database\Factory;
class ContactsFactory extends Factory {
protected $modelName = Contacts::class;
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition(): array
{
return [
];
}
}
This file contains the definition that will need to be setup.
3. Setting up The Factory Class Table of Contents
Below is an example using the UserFactory class:
<?php
namespace Database\Factories;
use App\Models\Contacts;
use Core\Lib\Database\Factory;
class ContactsFactory extends Factory {
protected $modelName = Contacts::class;
private $userId;
public function __construct(int $userId)
{
$this->userId = $userId;
parent::__construct();
}
public function definition(): array
{
'fname' = $this->faker->firstName();
'lname' = $this->faker->lastName();
'email' = $this->faker->unique()->safeEmail;
'address' = $this->faker->streetAddress();
'city' = $this->faker->city();
'state' = $this->faker->stateAbbr();
'zip' = $this->faker->postcode();
'home_phone' = $this->faker->phoneNumber();
'user_id' = $this->userId;
}
}
You will need to set the $modelName variable to the name of the model being used. Next, the array within the definition function needs to be filled out. We will use an associative array where the keys are all of the fields the model expects when creating a new record. Each key will be set to a value or call to a faker function.
4. Image Factories Table of Contents
Creating factories for images and uploading them requires a few extra steps. You will need to use a third-party library called Smknstd\FakerPicsumImages. Let’s go over this example for profile images.
<?php
namespace Database\Factories;
use Console\Helpers\Tools;
use Core\DB;
use Core\Models\ProfileImages;
use Core\Lib\Database\Factory;
use Smknstd\FakerPicsumImages\FakerPicsumImagesProvider;
class ProfileImageFactory extends Factory {
protected $modelName = ProfileImages::class;
private $userId;
public function __construct(int $userId)
{
$this->userId = $userId;
parent::__construct();
}
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition(): array
{
$this->faker->addProvider(new FakerPicsumImagesProvider($this->faker));
$basePath = 'storage' . DS . 'app' . DS . 'private' . DS . 'profile_images' . DS;
$uploadPath = $basePath . 'user_' . $this->userId . DS;
Tools::pathExists($uploadPath);
// Generate the image and get the actual filename from Faker
$actualFilePath = $this->faker->image($uploadPath, 200, 200, false, null, false, 'jpg');
// Extract only the filename
$imageFileName = basename($actualFilePath);
ProfileImages::findAllByUserId($this->userId);
$sort = ProfileImages::count();
return [
'user_id' => $this->userId,
'sort' => $sort,
'name' => $imageFileName,
'url' => $uploadPath . $imageFileName
];
}
}
When creating image factories you may want to implement a constructor. In the example above we provide the id of the user as a parameter for the function.
You will need to import the third-party library, use Smknstd\FakerPicsumImages\FakerPicsumImagesProvider;, and manage where the file will be uploaded. If the files do get saved but you are having trouble accessing them make sure the upload path is correct.
When uploading the image using the $this->faker->image function call we set the path, height, width, and file type. Next we setup information for the record. Finally we save the file and produce the appropriate output messages.
Ensure permissions are correct. This is suitable for test environments only.
5. Using Factories Table of Contents
A. Instantiating Factories
A few ways of creating a factory instance is shown below:
$userFactory1 = new UserFactory();
$userFactory1->... chained functions
$userFactory2 = (new UserFactory())->... chained functions
UserFactory::factory()->... chained functions
The factory() function is used to get an instance of a factory class for elegant syntax with function chaining.
B. create()
Use the create() function to insert a new record into the database.
Parameter
array $attributes- $attributes The an associative array attributes used to override default definition values.
Returns
array|object- The array of models that were created or a single object model if just one record is inserted.
This framework does not currently support the make() function.
Execution Order
The features of this framework assumes the current precedence order
definition()
→ sequence()
→ state()
→ explicit create() attributes
→ insert
→ afterCreating
C. States
States allows users to override default values generated in the definition function. These functions are created in your factory class.
The anonymous functions inside each callback accept the following parameters:
$data- Information from the definition function$attributes- Array passed as parameter to thecreatefunction.
Here are two different syntaxes for an example admin function from the UserFactory.
Example 1:
public function admin(): static {
return $this->state(function (array $data , array $attributes) {
return [
'acl' => json_encode(["Admin"])
];
});
}
Example 2:
public function admin(): static {
return $this->state(fn(array $data, array $attributes) => [
'acl' => json_encode(['Admin']),
]);
}
You can also invoke other factories using state as follows:
public function withImages(int $count = 2): static {
return $this->afterCreating(function (Users $user) use ($count) {
ProfileImageFactory::factory($user->id)->count($count)->create();
});
}
D. afterCreating
Use the afterCreating feature to perform actions after a record is created with a factory. Implement the configure() function in the parent Factory class with any additional task.
protected function configure(): static {
return $this->afterCreating(function (Users $user) {
(new ProfileImageFactory($user->id))->count(2)->create();
});
}
Chaining is also supported.
protected function configure(): static
{
return $this
->afterCreating(function (Users $user) {
(new ProfileImageFactory($user->id))
->count(2)
->create();
})
->afterCreating(function (Users $user) {
// Example: assign default role record
(new UserRoleFactory($user->id))
->create();
});
}
Under the hood afterCreating is not immutable like state and sequences. This allows the configure() method to register callbacks during construction without requiring reassignment.
Note
The configure() function is called automatically when the factory is instantiated.
E. Sequencing
You can use sequencing to override default definition values in a specific order. The sequence() function should be chained before calling create(). It works in conjunction with count() to alternate values across multiple insertions.
$factory3 = new UserFactory();
$factory3->count(4)->sequence(
['acl' => json_encode(["Admin"])],
['acl' => json_encode(["Vendor"])],
)->create();
F. Function Chaining
You can chain together function calls to modify the definition before a record is inserted or change the number of records to be created.
Some examples are shown below:
UserFactory::factory()->admin()->count(1)->create();
UserFactory::factory()->admin()->inactive()->create(['fname' => 'Jane', 'lname' => 'Doe']);
More on specific UserFactory state function in a later section.
G. Attributes
You can override default values in the definition file while calling the create function. You will need to supply an associative array where the key is the field and the value is what you want to be set instead.
UserFactory::factory()->create(['fname' => 'Jane', 'lname' => 'Doe']);
6. UserFactory Table of Contents
The UserFactory class is built-in to the framework. It contains everything you need to add a new user record and set states for various values.
Supplied state functions:
UserFactory::admin()- Setsaclvalue to["Admin"]UserFactory::deleted()- Setsdeletedto1UserFactory::inactive()- Setsinactiveto1UserFactory::loginAttempts()- Setslogin_attemptsto1UserFactory::resetPassword()- Setsreset_passwordto1UserFactory::withImages(int $count)- Creates profile images based on value provided as a parameter
7. Complete Example Table of Contents
Below is a complete example using all features:
UserFactory::factory()
->count(4)
->sequence(
['acl' => json_encode(["Admin"])],
['acl' => json_encode(["Vendor"])],
)
->admin()
->inactive()
->withImages(2)
->create([
'fname' => 'Jane', 'lname' => 'Doe'
]);