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 Laravel Package

xsolve-pl/model-factory

xsolve-pl/model-factory is a Laravel package for quickly generating model factories, helping you scaffold test data and simplify Eloquent model setup. It streamlines factory creation so you can focus on writing tests and building features.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and pin. The package is framework-agnostic (xsolve-pl/model-factory), so it is not auto-discovered by Laravel:

composer require xsolve-pl/model-factory

Check the composer.json PHP constraint first. The last tagged release is from September 2021, so expect ^7.4 || ^8.0-era constraints and no guarantees on PHP 8.2+. If Composer refuses to resolve against your platform, that is your first signal to evaluate before adopting.

Where to look first. Read the README top to bottom (it is short), then the two files that define the contract: the factory interface (the pair of methods that declare support and build a model) and the abstract base factory. Everything else — collections, laziness — is composition on top of those two. The test suite is the real documentation; it shows the intended ordering and nesting behaviour better than the prose.

First use case. Replace one hand-rolled JsonResource (or an anaemic toArray() on a domain object) with a factory for a single aggregate. The shape is always the same:

final class UserModelFactory extends AbstractModelFactory
{
    public function supports($object): bool
    {
        return $object instanceof User;
    }

    protected function createModel($object): UserModel
    {
        return new UserModel(
            $object->getId(),
            $object->getEmail(),
            $this->createModel($object->getAddress())   // nested, built lazily
        );
    }
}

Register the factory (and the collection that dispatches to it) in a service provider, resolve it in the controller, and return the resulting model to your serializer. Start with exactly one factory and one model. Do not convert the whole application; the package earns its keep only once you have polymorphic graphs.

Implementation Patterns

Factory-per-aggregate, dispatching by collection. The normal wiring is a collection (chain) of factories that you ask for a model; the collection walks the factories, asks supports(), and delegates to the first match. Keep one factory per concrete domain class, keep supports() narrow (instanceof against a specific class or a small interface), and keep the collection ordered from most specific to most general. A supports() that returns true for a base class will shadow every subclass registered after it.

Lazy nesting is the actual feature. Rather than recursively converting the whole object graph on construction, a model defers building its children until they are read. This is what makes it usable on rich domain objects: construction is cheap, cycles in the graph don't blow the stack, and you only pay for the branches your endpoint actually serialises. Use it deliberately — force resolution when you want eager behaviour, and leave it lazy for endpoints that read one or two fields.

Laravel integration. Bind the collection as a singleton in a ModelFactoryServiceProvider, and use the container's tagging or a simple config array to assemble the factory list:

$this->app->tag([
    UserModelFactory::class,
    AddressModelFactory::class,
    OrderModelFactory::class,
], 'model-factories');

$this->app->singleton(ModelFactoryCollection::class, fn ($app) =>
    new ModelFactoryCollection(...$app->tagged('model-factories'))
);

Then inject the collection into controllers, jobs, or console commands. Because the collection is a container binding and not a static registry, you can swap it wholesale in tests.

Eager-load before you convert. The factory has no idea about your persistence layer. If UserModel touches $user->orders, an unloaded relation means a query per user. Call ->with('orders.address') on the query builder before handing objects to the factory. Treat the factory as the last mile, never as a lazy-loading mechanism.

Collections of domain objects. Converting a list is a map over the factory, not a special API. Do it in one place (a small helper or a toModels() on the collection) so pagination metadata stays in the controller where it belongs.

Polymorphism. Where a domain object has subtypes (e.g. different payment methods), register one factory per subtype and let the collection resolve. This is the pattern's strongest case: the caller never writes a match on class name, and adding a new subtype is a new factory plus a registration line.

Serialization boundary. Models are plain objects with getters, deliberately not tied to HTTP. Implement JsonSerializable on the model, or hand them to the Symfony Serializer / JMS, or map to an array in an API Resource. Keeping that boundary explicit is the whole point — you can reuse the same models for queue payloads, exports, and HTTP.

Gotchas and Tips

The package is effectively unmaintained. Two stars, zero dependents, last release 2021. Budget for the possibility of forking it. The entire mechanism is roughly 100–150 lines of instanceof dispatch plus lazy getters — read it, and if it fights your PHP version, copy the two or three relevant classes into app/Support rather than patching a dependency you can't upgrade. Do not adopt this in a codebase where you can't afford that.

Compare against JsonResource honestly. For plain HTTP responses, Laravel's JsonResource + ResourceCollection already does 90% of this, with whenLoaded, whenCounted, and conditional relationships. The xsolve package wins only when you want DTOs decoupled from the HTTP layer and need the polymorphic dispatch. If your models never leave a controller, you're adding a dependency for nothing.

Silent misses. If no factory in the collection supports an object, you typically get null or an exception — depending on version, possibly late and far from the cause. Wrap the collection in a decorator that throws a descriptive exception naming the class, or register a NullModelFactory as the terminal fallback that logs and returns a placeholder. This single decorator saves hours of debugging.

Ordering bugs look like missing features. "My AdminUserModelFactory never runs" almost always means the general UserModelFactory is earlier in the collection. Assert order in a test — feed one object of each type through the collection and check the returned model class.

Laziness hides N+1s and exceptions. Because children resolve on access, a ModelNotFoundException-style failure surfaces during serialization, inside the response cycle, where stack traces are noisy. When debugging, force full resolution early in a test (assertSame([...], $model->toArray())) so failures point at the factory, not the framework's response pipeline.

Circular graphs. Lazy nesting protects you only while you don't resolve. The moment something calls json_encode on a model with a back-reference, you either recurse forever or hit a depth limit. Either break the cycle at the model level (drop the parent link) or add an identity-based guard.

Don't make it static. If the collection is a static/global registry, factories leak between tests and container rebinds are ignored. Keep it a bound singleton; in tests, instantiate a collection with a hand-picked subset of factories — that's the cleanest extension point in the whole package.

Extension points worth knowing. Subclass the abstract factory to add memoisation (same domain object → same model instance, which also kills cycles), to add logging in supports(), or to inject a serializer. A caching decorator around the collection is usually more valuable than any change to the factories themselves.

Pin the version and note the date. Since there are no upstream fixes coming, put a comment in composer.json or the service provider recording why the dependency exists and what it would take to remove it. Six months from now, that note is the difference between a refactor and an archaeology project.

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.
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
cgv/localized-resource