adamwathan/faktory
Faktory generates Laravel model factories automatically by inspecting your existing database tables. Instead of hand-writing factory definitions, it reads your schema to produce ready-to-use factories, speeding up test setup for legacy or already-built projects.
Faktory is a 2015-era PHP port of FactoryGirl: a static registry of named factory closures that build attribute arrays, with helpers to persist or instantiate objects. It predates Laravel's built-in model factories, so the first step is deciding whether you are evaluating it for a legacy codebase or for historical interest. Given that it is archived, last released in March 2015, and has ~22 stars, it should not be added to a new Laravel project.
Minimal steps for a legacy sandbox:
composer require --dev adamwathan/faktory
If Composer refuses on modern PHP, you will need a PHP 5.6/7.0 container or --ignore-platform-reqs; neither is a good production plan.
Look first at:
README.md — the only real API documentation.src/ — the entire package is a small static factory registry; the main entry point is Faktory\Factory (confirm exact class/namespace in the tagged release).tests/ — best source of truth for override and sequence behavior.Bootstrap factories once, not per test. Put definitions in tests/factories.php and load it from phpunit.xml's bootstrap attribute or from Tests\TestCase::setUp():
// tests/factories.php
use Faktory\Factory;
Factory::define('user', function ($f) {
return [
'name' => 'Test User',
'email' => '[email protected]',
'password' => password_hash('secret', PASSWORD_BCRYPT),
];
});
First use case — an integration test that needs a persisted Eloquent user:
$user = Factory::create('user');
$this->assertDatabaseHas('users', ['email' => '[email protected]']);
And the in-memory variant for unit tests:
$user = Factory::build('user'); // unsaved object/array, depending on definition
Separate definition from invocation. Keep all Factory::define() calls in one bootstrap file. Definitions are registered globally and resolved by string name, so scattering them across test classes makes ordering and collisions hard to reason about.
Use build by default, create only when the database matters. A large share of Laravel tests only need attribute arrays or unsaved models. Reserve create for integration tests that assert persistence, relationships, or queries. This mirrors User::factory()->make() versus ->create() and keeps suites fast.
Override per test, not per factory. Pass attribute overrides at call time:
$admin = Factory::create('user', [
'email' => '[email protected]',
'role' => 'admin',
]);
This is the main day-to-day ergonomic win: one canonical definition plus test-specific overrides.
Model associations inside definitions. Create related records in the closure and return the foreign key:
Factory::define('post', function ($f) {
return [
'title' => 'Example Post',
'body' => 'Body text',
'author_id' => Factory::create('user')->id,
];
});
Be aware this creates users as a side effect of building posts, which can surprise tests that count rows.
Simulate sequences with a counter. Unique columns such as email need uniqueness across a test run. Keep a static counter in your bootstrap and use it in the closure:
$userNumber = 0;
Factory::define('user', function ($f) use (&$userNumber) {
$userNumber++;
return [
'name' => "User {$userNumber}",
'email' => "user{$userNumber}@example.com",
];
});
Reset the counter between tests if you assert on exact values; the global state does not reset itself.
Emulate states with named factories. There is no states() API. Define admin as a separate factory name, or wrap the base definition in a helper function that merges overrides. Prefer explicit helper functions over stringly-typed factory names once you have more than a handful.
Integrate with RefreshDatabase. Faktory does not manage transactions or cleanup. Use Laravel's RefreshDatabase or DatabaseTransactions trait on the test case, exactly as you would with native factories.
Treat it as a migration source, not a destination. Map definitions one-for-one to Laravel factories:
Factory::define('user', fn ($f) => [...]) → UserFactory::definition()Factory::create('user', $overrides) → User::factory()->create($overrides)Factory::build('user', $overrides) → User::factory()->make($overrides)Factory::create('user') inside a definition → ->for(User::factory()) or ->has(...)A grep for Factory::define, Factory::create, and Factory::build gives you the full migration surface in most projects.
Abandoned and PHP-version hostile. The package predates PHP 7 type declarations, ::class conventions, and PSR-4 expectations in some places. Expect deprecation warnings, and do not expect fixes. If you must keep it alive, fork the single factory class rather than depending on the archived release.
Global static registry. Definitions leak across tests, and redefining the same name may either overwrite silently or throw depending on the release. Always run factories in a single bootstrap and avoid conditionally defining the same name in multiple files.
No lifecycle hooks. There is no afterCreating, afterMaking, or configure() equivalent. Anything that must happen after persistence (attach roles, fire events, seed pivot rows) has to be done manually in the test or wrapped in a helper function. This is the single biggest gap versus Laravel factories.
No relationship API. No for(), has(), recycle(), or shared parent instances. Every nested create call makes a new parent. For graphs of related models, write explicit helper functions and pass IDs in overrides.
Mass-assignment and fillable traps. Because create typically funnels attributes into an Eloquent model, columns not in $fillable are dropped silently. If a factory "works" but a column stays null, check $fillable before suspecting Faktory.
Password hashing. Hashing on every factory call is slow. Use a fixed bcrypt hash string or Hash::make once and reuse it in the definition, especially if you create many users.
Unique constraint collisions. Without a sequence primitive, parallel test runs and repeated create calls will collide on unique columns. Add a counter or random suffix, and reset it in setUp if needed.
Debugging is primitive. Put var_dump()/dd() inside the definition closure or dump the returned array before passing it to the model. Error messages for missing factory names are terse; check spelling and that the bootstrap file was actually loaded.
Extension points are essentially absent. There is no config file, no provider to register custom resolvers, and no plugin surface. If you need behavior beyond define/build/create, isolate it in your own wrapper class so the rest of the suite does not depend on Faktory's internals.
Recommendation. Use Faktory only to understand where Laravel's factories came from, or as a temporary bridge in an old test suite. For day-to-day work in any supported Laravel version, use the framework's class-based model factories; they cover every pattern above with lifecycle hooks, states, relationships, and active maintenance.
How can I help you explore Laravel packages today?