Kazem
Posted on August 31, 2023
During the development process, there are times when we need to evaluate the functionality of a system by using data. In Laravel, we have two useful tools called seeders and factories that help us generate random data. In this article, we’ll explore these tools and learn how to use them effectively.
The factory class in Laravel acts like a factory that generates random data for our database tables. If you navigate to the database/factories
directory in your project, you’ll find a class called UserFactory
:
class UserFactory extends Factory
{
public function definition(): array
{
return [
'name' => fake()->name(),
'email' => fake()->unique()->safeEmail(),
'email_verified_at' => now(),
'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password
'remember_token' => Str::random(10),
];
}
}
In each factory class, there is an important function called definition
that specifies how each attribute should be filled out. To accomplish this, Laravel utilizes a powerful package called fakerPHP, which can generate random data such as names, sentences, paragraphs, and even images. You can find more information about the helper functions and features of this package at the following link.
Another point in UserFactory
class is that the string that is considered a fixed password is the hashed word “password”.
Laravel includes a variety of global helper PHP functions, and one of them is called Str
. This helper function is useful for working with strings, such as generating random strings. You can refer to the documentation for this helper function at here.
Now, let’s get back to our project and generate a factory for our tags. To create a factory, we can use the following artisan command:
php artisan make:factory TagFactory
It’s generally recommended to use a singular name for factory classes. However, if you want to specify the model while generating the factory, you can use the “-m” option in the command, like this:
php artisan make:factory TagFactory -m Tag
In our Tag model, we only have a simple “name” attribute, which can be defined as follows:
use Illuminate\Database\Eloquent\Factories\HasFactory;
class Tag extends Model
{
use HasFactory;
}
Once we have set up the factory, we can use it to create instances of our model. To do this, simply call the factory method whenever you want to create a model instance.
You can also control the number of random data entries you want to generate. You can achieve this by using either the factory function or the count function. For example, if you want to create 5 random tags, you can use the following code:
Tag::factory(5)->create();
Furthermore, it’s possible to define a specific attribute with a desired value while generating the data. For instance, if you want to create 5 users with the name “test,” but you don’t care about the other attributes, you can use the following code:
User::factory(5)->create(['name' => 'test']);
In our project, we currently have two factories for our user and tag models. Now, we need to create another factory for our task model.
When defining the factory model, it is important to consider the relational model. In our task model, each task is associated with a user. So, what we want to achieve is creating a random task with the id of a given user.
However, during testing your application, there might be situations where no user has been created in the environment. In such cases, generating a random user could be a viable solution. To accomplish this, you can use factory model again just like bellow:
public function definition(): array
{
return [
"title" => fake()->sentence(),
"description" => fake()->paragraph(),
"expired_at" => fake()->dateTimeThisMonth(),
"user_id" => User::factory(),
];
}
If you run the code in Tinker, you will notice that a new user has been created for the task model.
Also we have the option to specify a user_id when generating a task. However, I suggest refactoring the code to utilize the users that are likely to already exist in database:
public function definition(): array
{
return [
"title" => fake()->sentence(),
"description" => fake()->paragraph(),
"expired_at" => fake()->dateTimeThisMonth(),
"user_id" => User::query()->inRandomOrder()->first()?->id ??
User::factory(),
];
}
To generate random data more efficiently in Laravel, we have a better solution than using Tinker. It’s called a seeder.
In the database/seeders
directory, you will find a class named DatabaseSeeder
, which allows us to manage the generation of random data.
Let's update the DatabaseSeeder
class as shown below, and then execute an artisan command to run this function.
class DatabaseSeeder extends Seeder
{
public function run(): void
{
Task::factory()->create();
}
}
And here is the artisan command for seeding:
php artisan db:seed
That’s it!
With Seeder, we can easily create a random task record in our database using artisan. Additionally, we have the flexibility to create multiple seeders for different scenarios. To create a seeder, you simply need to run php artisan make:seed <seeder-name>
command.
Now, let’s create a TaskSeeder
that generates 5 random task records, with each task associated with two random tags:
namespace Database\Seeders;
use App\Models\Tag;
use App\Models\Task;
use Illuminate\Database\Seeder;
class TaskSeeder extends Seeder
{
public function run(): void
{
Task::factory(5)->hasTags(2)->create();
}
}
To run this seeder, you can use the artisan command by specifying your desired seed class.
php artisan db:seed TaskSeeder
Or you can use call method inside DatabaseSeeder
to execute your seed classes:
class DatabaseSeeder extends Seeder
{
public function run(): void
{
$this->call(TaskSeeder::class);
}
}
Initial data model
Well, sometimes there are certain values that are need to be initialized, such as the status of an article (e.g., published or draft) or the status of a transaction (e.g., paid or non-payment).
I want to apply a similar approach to tags in this project. We have a couple of options to achieve this.
One option is to store the initial values in a configuration file. To do this, we can create a PHP file called “defaults” at the specified config path and define the desired values there:
<?php
return [
'tags' => ['php', 'laravel', 'develop', 'backend']
];
We can then initialize these values using a migration file.
Alternatively, we can initialize these values using a seeder. This means we would define a seeder like TagSeeder
that populates the default value in the database.
Both approaches work, but I prefer to use seeder because it’s more clear:
Let’s define TagSeeder
and initialize it’s values:
class TagSeeder extends Seeder
{
public function run(): void
{
foreach (config('defaults.tags') as $value) {
Tag::firstOrCreate(['name' => $value]);
}
}
}
As you can see, the helper function config in Laravel is utilized to retrieve static information from config files.
Here I use firstOrCreate
method which adds a new tag record only if it hasn’t been previously added.
Great! Now, let’s all come together and work on implementing a scenario like the one described below inside our TaskSeeder
:
namespace Database\Seeders;
use App\Models\Tag;
use App\Models\Task;
use App\Models\User;
use Illuminate\Database\Seeder;
class TaskSeeder extends Seeder
{
public function run(): void
{
// create a user with specified credentials
$user = User::factory()->create(['email' => 'test@test.dev']);
// init tag with defined value
$this->call(TagSeeder::class);
// create 5 random tasks for the user
$tasks = Task::factory(5)->create(['user_id' => $user->id]);
// assosiate each generated tasks with 2 predefined tags
foreach ($tasks as $task) {
$tags = Tag::query()->inRandomOrder()->take(2)->pluck('id');
$task->tags()->attach($tags);
}
}
}
Now let’s rebuild our database and fill it with this defined seeder. To rebuild the database you can run migrate:fresh
artisan command which will drop all tables and re-run all of your migrations:
And if you login with the user you’ll see the tasks generated successfully:
I hope this explanation clarifies the process of using seeders and factories in Laravel. If you have any further questions, feel free to ask!
Posted on August 31, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.