I supercharged Laravel’s aging Seeder
Yeah, it’s a decade old thing
I’m not going to lie, but one of the things that even I don’t put too much attention when developing an application are Seeders, those classes that help you populate a database with fake-but-correct records. These are great utilities when you need to deal with testing or staging environments.
Seeding system in Laravel have been around a decade ago without major updates or rework. The system has been kept as-is because nobody took the time to ask themselves “How to make it more convenient for the developer?”, and every time Laravel does a major update, Seeders are e big oversight.
And I get that. For simple projects, the included seeding concept will work wonders since you only need to call a few Model Factories. The problem comes when you’re making more than just a simple project, and that’s when you stop developing with the seeder and start fighting the seeder itself.
With the hopes to rework the Database Seeding system on Laravel, I pushed a PR to the framework. The objective was adding a revamped alternative while keeping the legacy seeding procedure intact. After a couple of days, it was shot down by Taylor Otwell himself, suggesting reworking the new seeding system into a package.
And I did.
You deserve a better Database Seeder
I created Laragear Populate, a library that extends the Seeder with a new set of features for those who require a better experience when developing seeders, especially if you have more than one seeding path.
I made the new seeding system with three goals in mind:
- Informative: By using “seed steps”, you can know what is being populated with.
- Flexible: Seed Steps are skippable and resolved by Dependency Injection, so it’s easy to bring the tools you need for complex tasks.
- Backwards compatible: You shouldn’t be forced to change your legacy Seeders.
What are the features? There is a lot, but I guess you would be interested in the most important ones.
Seed Steps are more detailed seeding
Instead of using a single run()
method in a seeder and work your way from there from scratch, you can use Seed Steps, which is the core concept of the new Database Seeding system.
Seed Steps are just public methods (that start with “seed” or use the SeedStep
attribute) that populate the database with a set of records.
namespace Database\Seeders;
use App\Models\User;
use Laragear\Populate\Seeder;
use Laragear\Populate\Attributes\SeedStep;
class UserSeeder extends Seeder
{
public function seedNormalUsers()
{
User::factory(5)->create();
}
public function seedBannedUsers(TicketGenerator $ticket)
{
User::factory(2)->banned()->has($ticker->createFake())->create();
}
#[SeedStep(as: 'Seed VIP users')]
public function vipUsers()
{
// Using the Attribute also works too!
}
}
Inside a Seed Step you can use Dependency Injection, and even skip a step safely without fearing of stopping the whole seeding operation.
public function seedDisabledUsers()
{
if (User::query()->disabled()->exist()) {
$this->skip('There are already disabled users in the database');
}
// ...
}
When a Seed Step executes, it will be shown in the console using its name and status.
php artisan db:seed
INFO Seeding database.
Database\Seeders\UserSeeder ...................................... RUNNING
~ Seed normal users ................................................. DONE
~ Seed banned users ................................................. DONE
~ Seed VIP users .................................................... DONE
Database\Seeders\UserSeeder .................................. 187 ms DONE
No more SQLite slowness
SQLite is a good database, but it struggles when writing records to the database file. Because Laragear Populate wraps each Seed Step into a Database Transaction (can be disabled), SQLite will perform faster out of the box.
Calling other Seeders
You can also call other seeders through the call()
method — that hasn’t changed. If your seeder uses Seed Steps, you can pass parameters for each Seed Step using the method name as key and the values in an array.
public function seedUsersWithComments()
{
$users = User::factory(3)->create();
$this->call(CommentSeeder::class, [
'seedUserComments' => ['users' => $users]
]);
}
public function seedUserComments($users = [])
{
foreach($users as $user) {
Comment::factory()->belongsTo($user)->create();
}
}
Continuing a failed seeding
When developing a seeder, most of the time you will find yourself with failing seeding for different reasons. Sometimes it’s a UNIQUE
constraint violation, sometimes it’s your fault.
Instead of seeding the whole database again hoping the fix is correct, you can use the --continue
option in the db:seed
Artisan command and continue just where the seeding stopped.
php artisan db:seed --continue
You will get an output pretty much like the following:
php artisan db:seed
INFO Seeding database.
Database\Seeders\UserSeeder ...................................... RUNNING
~ Seed normal users ............................................. CONTINUE
~ Seed banned users ............................................. CONTINUE
~ Seed VIP users ................................................ CONTINUE
Database\Seeders\UserSeeder .................................... 0 ms DONE
Database\Seeders\CommentSeeder ................................... RUNNING
~ Seed guest comments ........................................... CONTINUE
~ Seed user comments ................................................ DONE
~ Seed vip comments ................................................. DONE
- Seed moderated comments ........................................... DONE
Database\Seeders\CommentSeeder ............................... 107 ms DONE
Apart from that, also:
- Running Logic before and after executing the Seed Steps
- Error handling
- The Artisan command
make:super-seeder
included
Just give it a spin in your project and tell me how it went for you.