meritum / model-factory
Model factory builder for the Meritum ecosystem
Requires
- php: ^8.4
- fakerphp/faker: ^1.23
- georgeff/kernel: ^1.10
- meritum/database: ^1.3
Requires (Dev)
- bnf/phpstan-psr-container: ^1.0
- phpstan/phpstan: ^2.0
- phpunit/phpunit: ^11.0
- squizlabs/php_codesniffer: ^3.11
README
Model factory builder for the Meritum ecosystem — defines Faker-backed attribute sets for meritum/database models and builds or persists them in tests and seeders. Each model gets its own factory Definition, registered via the model.factory.definitions kernel tag — the same pattern meritum/cli uses for commands — so any project can ship its own definitions and have them discovered automatically, with no central registry to maintain.
Requirements
- PHP 8.4+
georgeff/kernel^1.10meritum/database^1.3fakerphp/faker^1.23
Installation
composer require meritum/model-factory
Usage
Defining a factory
Extend Definition once per model. It's constructed with a Faker\Generator, and returns the model's default attribute set:
use Faker\Generator; use Meritum\ModelFactory\Definition; final class PostDefinition extends Definition { public function getModelClass(): string { return Post::class; } public function getDefinition(): array { return [ 'title' => $this->faker->sentence, 'body' => $this->faker->paragraph, ]; } }
Registering the module
Add ModelFactoryModule to your kernel. It requires a DatabaseManagerInterface to already be registered — the module doesn't build one itself, the same way meritum/structured-logging expects a LoggerInterface to already exist:
use Meritum\ModelFactory\ModelFactoryModule; use Georgeff\Database\Contract\DatabaseManagerInterface; $kernel->define(DatabaseManagerInterface::class, fn() => /* ... */); $kernel->addModule(new ModelFactoryModule());
Each Definition subclass is registered like any other tagged service — the module doesn't scan for them, it resolves whatever's tagged:
use Faker\Generator; use Psr\Container\ContainerInterface; use Meritum\ModelFactory\FactoryOption; $kernel->define(PostDefinition::class, fn(ContainerInterface $c) => new PostDefinition($c->get(Generator::class))) ->tag(FactoryOption::DefinitionTag->value);
Building and persisting models
Resolve ModelFactoryInterface from the container and use make(), create(), or collection():
use Meritum\ModelFactory\ModelFactoryInterface; $factory = $kernel->getContainer()->get(ModelFactoryInterface::class); // Build without persisting — attributes come from the Definition $post = $factory->make(Post::class); // Build with specific attributes overriding the Definition's defaults $post = $factory->make(Post::class, ['title' => 'A specific title']); // Build and persist $post = $factory->create(Post::class); // Build (or persist) several at once $posts = $factory->collection(10, Post::class); // persisted, keyed by primary key $posts = $factory->collection(10, Post::class, [], false); // not persisted, keyed by index
make()/create()/collection() are generic over the model class — $factory->make(Post::class) is inferred as returning Post, not just the base Model type, so callers get proper static typing without needing to annotate anything themselves.