stemble/laravel-doctrine-factory
Use Laravel's modern Eloquent factories with Doctrine entities. Drop-in replacement for the official frozen pre-8.x factory system, adding class-based factories, first-class states, relationship helpers, sequences, recycle, and afterMaking/afterCreating callbacks.
Architecture fit. The package is a narrow but well-aimed adapter: it subclasses Laravel's Illuminate\Database\Eloquent\Factories\Factory as DoctrineFactory, so the unmodified HasFactory trait, definition(), state(), sequence(), recycle(), and the for()/has() helpers all resolve through Laravel-native code paths against Doctrine entities. That is the right architectural bet — it means the team's existing Eloquent factory knowledge and Laravel's own documentation transfer directly, rather than requiring a parallel mental model. Fit is binary, however: it is only relevant if the application already runs Doctrine via laravel-doctrine/orm and currently suffers from that package's frozen pre-8.x closure-based factory API. For Eloquent-based services the package has zero addressable value.
Integration feasibility. Low effort for the happy path — composer require, trait on the entity, a factory class extending DoctrineFactory, and filename/namespace mirroring under database/factories/. The friction is concentrated in Doctrine-specific reality rather than package API: constructor-aware hydration (required constructor params must appear in definition() or MissingConstructorAttributesException fires), reflection-based property writes that bypass setters, and bidirectional relationship sync requiring explicit configure()/afterMaking() wiring. These are documented honestly in the README, which is a positive signal for a low-adoption package.
Technical risk. Three material risks, in order:
Factory ties the package to Laravel's internal factory implementation (state arrays, attribute expansion, model instancing). Laravel majors/minors are the breakage vector, not Doctrine. An unmaintained release here can block a Laravel upgrade across the whole test suite — a schedule risk, not just a bug risk.make(). make() calls EntityManager::persist(), meaning entities are registered in the identity map and will be inserted by the next flush() anywhere in the process. This is a deliberate and defensible design (it enables single-flush graph building, which is a genuine Doctrine win), but it converts "harmless test setup" into "unexpected INSERT" if any code path flushes. This is the highest-probability production/fixture incident in the package.readonly, promoted constructors, union/nullable types, and setters with side effects (validation, eventing, collection sync) are unverified edge cases. Failures may be silent (wrong data, missing collections) rather than loud.Vendor risk is the headline governance issue: 2 stars, 0 dependents, MIT, README/changelog/releases/CI present, recent release — i.e. functionally credible, effectively single-maintainer, no ecosystem backstop.
Key questions to resolve before committing:
laravel/framework, laravel-doctrine/orm, and doctrine/orm (2.x vs 3.x) versions are in the CI matrix? Is there a stated support window?readonly properties, and variadic/promoted constructors?count(n)->create() — one flush or n? This determines seeder performance.RefreshDatabase/transactional test isolation and EntityManager::clear()?Stack fit. Target profile: Laravel 10–12, PHP 8.1+, laravel-doctrine/orm, entities under a namespace mirrored into database/factories/. If the codebase is Eloquent-first, do not introduce this. If it is Doctrine-first with a test suite or seeder surface that depends on factories, the value is high because the alternative is hand-rolled fixture builders — which is what most Doctrine teams end up maintaining badly.
Migration path. Non-destructive and incremental by design. The legacy $factory->define(...) system and DoctrineFactory classes can coexist, so migration can proceed entity-by-entity with no flag day. Recommended shape:
make()/create() semantics, and the configure() sync pattern against your actual entity style.App\Support\Testing\BaseDoctrineFactory extends DoctrineFactory) and require all factories to extend it. This is the single highest-leverage TPM decision here — it gives you one seam to shim, patch, or replace the vendor class when Laravel or the package moves.configure() for bidirectional sync, afterMaking() vs afterCreating() guidance, make()-then-single-flush() for graph building, and "pass through constructor or call setter explicitly."Compatibility. Pin the package to an exact version; do not float. Add it to the CI matrix alongside your Laravel and Doctrine versions so upgrade PRs surface breakage at dependency-bump time rather than mid-feature. Treat the Factory base class as the compatibility surface and assert it with a small contract test suite of your own (one factory per hydration shape: constructor-only, reflection-only, mixed, collection-valued).
Sequencing relative to other work. Schedule before, not during, a Laravel major upgrade — the package must be validated against the target Laravel version first, otherwise you inherit an unresolvable blocker. Do not sequence it behind a Doctrine 2→3 migration without re-validating, since identity-map and hydration behavior is where the two interact.
Maintenance. Ongoing cost is low in steady state and spiky at framework upgrades. Budget an explicit owner and roughly one to two engineer-days per quarter for version-matrix validation, plus a contingency of two to five days for a fork-and-patch when a Laravel release breaks the subclass. Because the license is MIT and the package is small, fork-and-vendor is a viable, pre-approved fallback — decide this up front and write it into the ADR rather than discovering it during an upgrade freeze. Do not let the internal base class be the only insulation; also keep all factory definitions free of package-specific idioms that a fork could not preserve.
Support. Community-only, no SLA, no vendor escalation. Effective support is the README's compatibility grid plus your own contract tests. Mitigate with: an internal conventions doc, a code-review checklist for factories, and a rule that any new entity hydration shape gets a fixture test. The presence of CI, changelog, and releases is the main reassurance; the 2-star/0-dependent profile means you are an early adopter and should behave like one.
Scaling. Two dimensions:
make()-persists semantics are actually a scaling asset — build a graph with make() and flush once instead of flushing per entity. Codify that pattern or teams will write create() in loops and pay for it.count(n)->create() runs risks memory pressure and slow flushes. Chunk large seeders and EntityManager::clear() between batches; verify whether count(n)->create() batches its flush before relying on it.Failure modes to instrument and document:
| Failure | Symptom | Detection |
|---|---|---|
Stray flush() after make() |
Unintended INSERTs in tests or partial data | Assert row counts in fixture tests; ban bare make() outside graph-building |
| Setter bypass via reflection | Validation/eventing/collection sync silently skipped | Review checklist; prefer constructor args or explicit setter in afterMaking() |
| Inverse side not synced | $user->getPosts() empty in memory, correct after refresh |
Default configure() sync per factory; assert in entity tests |
| Missing constructor attribute | MissingConstructorAttributesException |
Loud — caught at first run |
Laravel upgrade breaks Factory subclass |
Suite-wide failures | CI matrix on dependency bumps |
| Maintainer abandonment | No fix for above | Fork pre-approved; internal base class |
Ramp-up. For a developer fluent in Eloquent factories, the API is roughly a half-day; the real learning cost is the three Doctrine deltas — make() persists, setters are bypassed, inverse sides are unsynced. Budget one short workshop plus a written conventions page, and expect the first week of PRs to need review on relationship wiring. Net: cheap onboarding, with the caveat that the failure modes are quiet rather than loud, so onboarding must be paired with conventions rather than left to discovery.
TPM recommendation. Conditional adopt for Doctrine-based services with a meaningful factory/test-fixture surface: high ergonomic gain, low integration cost, manageable but real vendor risk that is cheaply hedged by pinning, an internal base class, a CI compatibility matrix, and a pre-approved fork path. Reject for Eloquent-based services, and defer for teams with only a handful of fixtures where plain builders carry less governance overhead.
How can I help you explore Laravel packages today?