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

Model Factory Bundle Laravel Package

xsolve-pl/model-factory-bundle

A Symfony bundle that brings Laravel-style model factories to PHP projects, helping you generate test data and fixtures for Doctrine entities with reusable definitions, associations, and customizable attributes.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and register

composer require --dev xsolve-pl/model-factory-bundle

Register the bundle in config/bundles.php (it returns a Bundle class, so a single line is usually enough — no .env keys, no database tables). Because factories are a test/dev concern, install it under require-dev and only enable the bundle in dev/test environments.

Where to look first

Before writing anything, open the package source in vendor/xsolve-pl/model-factory-bundle/src/ and read, in this order:

  1. The abstract factory class (the one you extend) — its abstract methods define the contract: which model class the factory targets and how default attributes are declared.
  2. The registry / locator class — how a factory is retrieved by model class or name.
  3. The DI extension — the service tag used to auto-register factories, and whether autoconfiguration is on by default.

The method names are the API; this bundle has a small surface area, so 10 minutes of reading beats guessing from the README.

First use case: a factory for one Doctrine entity

final class UserFactory extends AbstractModelFactory
{
    protected function getModelClass(): string
    {
        return User::class;
    }

    protected function getDefaultAttributes(): array
    {
        return [
            'email' => $this->faker->unique()->safeEmail(),
            'name'  => $this->faker->name(),
            'roles' => ['ROLE_USER'],
        ];
    }
}

Register it like any service (with autoconfigure: true, or the bundle's tag explicitly if the extension requires it), then in a test:

$user = $this->factory(User::class)->create(['name' => 'Ada']);

The mental model is Laravel-style: defaults in one place, per-call overrides on top, persistence handled for you. Your first win is usually replacing 15 lines of hand-built entity setup in a PHPUnit test with one call.

Implementation Patterns

One factory per aggregate root. Don't create a factory per table. A factory for Order should be able to build a valid Order with its line items and customer, so tests never need to assemble a graph by hand.

Defaults + overrides is the core workflow. getDefaultAttributes() produces a valid object every time; individual tests override only the attributes that matter to the assertion. If a test overrides five attributes, your defaults are probably wrong.

States as fluent methods. Model common variants as named methods rather than repeating override arrays:

public function admin(): static
{
    return $this->state(['roles' => ['ROLE_ADMIN']]);
}

public function withOrders(int $count = 3): static
{
    return $this->afterCreating(fn (User $u) => /* attach orders */);
}

Verify the exact state() / afterCreating() names in the abstract class — but the pattern (immutable variant, returns a cloned factory) is the one to implement even if you have to add those helpers yourself in a project-level base class.

Separate "make" from "create". Pure unit tests want an entity that isn't persisted; integration tests want it flushed. If the bundle only exposes one entry point, add a thin wrapper in your own base factory so the distinction is explicit. Mixing the two is the most common source of slow, DB-coupled tests.

Shared traits for cross-cutting attributes. Timestamps, slugs, UUIDs, and tenant IDs belong in traits (WithTimestamps, WithTenant) that factories use, not copy-pasted into every getDefaultAttributes().

Deterministic Faker. For snapshot tests or failing-test reproduction, seed the generator ($faker->seed(1234)) in a test bootstrap or via config. Combine with unique() on constrained columns (email, slug, uuid) to avoid collisions across a suite.

Integration points. The highest-leverage places are: Doctrine fixtures/doctrine:fixtures:load, Behat/Symfony KernelTestCase contexts, and PHPUnit data providers. For data providers, build plain arrays there and only invoke factories inside the test method — the kernel and DB may not be ready when providers run.

Wrap it. Put a single AbstractAppFactory between your factories and the bundle's abstract class. It costs nothing now and means a future swap (or a bundle abandonment) touches one file instead of fifty.

Gotchas and Tips

Maturity is the biggest risk. Twelve stars, zero dependents, last release January 2023. Pin the version exactly in composer.json, and check it against your current PHP and Symfony major version before adopting. The wrapping pattern above is not optional here — treat the package as an internal implementation detail.

Autoconfiguration silently failing. If $factory->create() throws "no factory for App\Entity\Foo", the culprit is almost always service registration: _defaults: autoconfigure: true missing in services.yaml, or the factory living outside the resource glob. Debug with:

bin/console debug:container --tag=<the bundle's tag>

Flushing strategy. create() typically persists and flushes per call. In a fixture loading a thousand rows, that's a thousand round-trips. Batch: build with make-style calls, then a single flush() in your loader. If the bundle flushes internally, wrap bulk creation in a transaction.

Identity map and unique constraints. Faker's unique() guarantees uniqueness within one generator instance, not across the whole suite. If you seed a database incrementally across tests without rollback, reset the Faker generator or use a factory-level monotonic counter (user_{n}@example.com).

Stateful factory instances. If variants mutate $this instead of cloning, a ->admin() call leaks into the next test that reuses the same service. Symfony services are shared by default — either make the variant methods return clone $this or mark the factory services non-shared.

Association recursion. UserFactory creating a Company whose factory creates an Owner whose factory creates a User will loop or explode. Take related entities as optional constructor parameters (create([], ['company' => $company])) and only auto-create them when absent.

Extension points worth knowing. The registry is the natural place to decorate: alias it in services.yaml and add logging or assertions ("this test created 400 entities") without forking the bundle. The abstract factory's attribute hook is the place to inject tenant/context defaults globally.

Don't use it for production seeding. Factories are built for test-shaped data volumes and test-shaped realism. For production demo data or large fixtures, write dedicated seeders and keep factories out of the runtime container entirely.

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.
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums
rlacerda83/query-parser
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings