Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Seed Cascade Laravel Package

hedronium/seed-cascade

Seed Cascade is a Laravel package for running database seeders in dependency order, letting you define cascading relationships between seeders so related records are created automatically without manual ordering or foreign-key conflicts.

View on GitHub
Deep Wiki
Context7

Getting Started

Install the package as a dev dependency:

composer require --dev hedronium/seed-cascade

Where to look first

  1. README.md — the exact API surface is small and the README is the canonical reference; class and method names matter here.
  2. src/ — the base Seeder/Cascade classes. The thing you care about is the abstract seed($from, $to) contract your child seeders must implement, and how the cascade passes ranges down through the chain.
  3. tests/ (if present) — usually the fastest way to see a working two-level cascade.

Smallest useful first case — seed users, cascade into posts, without loading a million rows into memory:

use Hedronium\SeedCascade\Seeder;

class UsersSeeder extends Seeder
{
    public function seed($from, $to)
    {
        $now  = now();
        $rows = [];

        for ($i = $from; $i <= $to; $i++) {
            $rows[] = [
                'id'         => $i,
                'name'       => "User {$i}",
                'email'      => "user{$i}@example.test",
                'created_at' => $now,
                'updated_at' => $now,
            ];
        }

        User::insert($rows);
    }
}
use Hedronium\SeedCascade\Cascade;

class DatabaseSeeder extends \Illuminate\Database\Seeder
{
    public function run()
    {
        Cascade::from(1)->to(100_000)
            ->add(UsersSeeder::class)
            ->add(PostsSeeder::class)   // receives the cascaded 1..100000 range
            ->run();
    }
}

Run it with php artisan db:seed. If the API names differ in your version, the principle is the same: you declare a numeric range once, and the cascade slices it into chunks for each seeder in the chain.

Implementation Patterns

Let the cascade own the loop. The value of the package is that your seed($from, $to) method is called once per chunk. Do not loop back over the whole range yourself or call factory()->count($to)->create() — that reintroduces the memory problem you installed the package to avoid.

Cascade foreign keys by range arithmetic, not queries. Because every seeder in the chain is driven by the same numeric range, you can derive child FKs deterministically instead of querying the parent:

class PostsSeeder extends Seeder
{
    public function seed($from, $to)
    {
        $userMax = 100_000;
        $rows = [];

        for ($i = $from; $i <= $to; $i++) {
            $rows[] = [
                'id'         => $i,
                'user_id'    => ($i % $userMax) + 1, // guaranteed to exist
                'title'      => "Post {$i}",
                'created_at' => now(),
                'updated_at' => now(),
            ];
        }

        Post::insert($rows);
    }
}

This pattern is the sweet spot: no User::inRandomOrder()->first() per row, no whereIn on foreign key sets, no correlation bookkeeping between phases.

Bulk insert inside the chunk. One insert() per chunk is 50–100× faster than one create() per row. If you need model events, override the tradeoff deliberately (see Gotchas).

Seed in strict dependency order. Register parent seeders before children in the cascade chain. If the package doesn't enforce ordering, you'll get foreign key violations that are easy to misdiagnose as chunking bugs.

Wrap DatabaseSeeder in a reset. For repeatable local seeding, Schema::disableForeignKeyConstraints() + truncate at the top, or simply migrate:fresh --seed. The package doesn't reset tables for you.

Compose with factories where types matter. Factories are fine for generating realistic column values — call factory(User::class)->make() once per chunk and stamp the IDs manually:

$make = factory(User::class)->make(['id' => null])->toArray();

…but only if per-chunk allocation is bounded. A pure for loop of arrays is faster and more predictable for pure-volume seeding.

Use it as a one-shot dev/staging fixture, not as a production data migration tool. The 33-star footprint and lack of dependents mean you're betting on your own ability to read three files when something breaks.

Gotchas and Tips

Auto-increment collisions. If children use explicit IDs and parents also do, and either table has AUTO_INCREMENT, insert order and gap-filling can produce duplicate-key errors on re-run. Always reset tables first, and if you must use explicit IDs, insert parents fully before children in the same process.

Model::insert() skips events, observers, mutators-by-accessor, and casts. Soft deletes, deleted_at, JSON casts, password hashing — all silently skipped. Use Hash::make() explicitly, set timestamps explicitly, and remember Model::insert() does not call boot() observers. If you need any of that, drop to DB::table()->insert() and be honest about it, or chunk with create() and accept the slower path.

now() inside the inner loop is a real cost. At 100k rows × 3 calls you'll notice. Hoist it per chunk as shown above.

max_allowed_packet and MySQL. A chunk of 5,000 wide rows can exceed the default 64 MB packet on some setups. Tune chunk size to your row width; 500–2,000 is safe for typical Laravel models.

Disable FK checks when chains get long. DB::statement('SET FOREIGN_KEY_CHECKS=0') around the cascade run is the pragmatic answer when ordering bugs are easier to sidestep than to fix.

SQLite behaves differently. The cascade logic is pure PHP so it works, but SQLite's lack of real concurrency and its AUTOINCREMENT quirks mean a chain that works on MySQL/Postgres may produce odd IDs on SQLite. Test locally on SQLite, validate on your staging engine.

Truncate does not reset child sequences cleanly across drivers. Postgres needs TRUNCATE ... RESTART IDENTITY CASCADE; MySQL uses AUTO_INCREMENT = 1. The package won't handle this — add it to your DatabaseSeeder::run() before the cascade.

Debugging a stalled cascade. If it hangs, the culprit is almost always a query executed inside seed() (an existence check, a random(), a whereIn with a huge list) — that query runs total / chunk_size times. Log $from/$to on entry, and if the same range repeats, the seeder is re-throwing an exception the cascade is retrying.

Extension points are thin by design. Because the package is small, the practical extension is to subclass the base seeder and override the chunk-size or range-formatting hooks where they exist, or wrap Cascade::run() in your own helper that sets up transactions, foreign key checks, and truncation. Don't expect a plugin ecosystem — write a five-line wrapper instead.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle