icanhazstring/foundry-bundle
Symfony bundle that integrates Foundry's PHP object factories into your app, giving you container-aware configuration for building test fixtures and model instances with less boilerplate. Use it to wire foundries as services and keep fixture setup clean and reusable across tests. Ideal for Symfony p...
Before you composer require anything, check the package is still installable in your stack. This bundle has exactly one meaningful release history (original 2021, plus 0.3.0, which is a pure rename), 0 stars, 0 dependents, and was written against the Symfony 4/5 + Foundry 1.x era. Run a dry-run first:
composer require --dev icanhazstring/foundry-bundle:^0.3 --dry-run
If Composer resolves it, verify what it wants in return (composer why-not icanhazstring/foundry-bundle and inspect its composer.json for symfony/* and zenstruck/foundry constraints). If it blocks on Symfony 6/7 or Foundry 2.x, treat the bundle as reference source code, not a dependency — copy the compiler pass into your own src/ (see Gotchas).
0.3.0 is a breaking rename, and it is the only "feature" in the release: the namespace moved to lowercase-vendor icanhazstring\FoundryBundle and the bundle class is now simply FoundryBundle. If you are on 0.2.x, this is a find-and-replace upgrade (class reference in bundles.php, any use statements, and possibly your config key — see below). If you are new, the namespace in this assessment is now confirmed rather than inferred.
config/bundles.php:return [
// ...
Symfony\Bundle\FrameworkBundle\FrameworkBundle::class => ['all' => true],
Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true],
Zenstruck\Foundry\ZenstruckFoundryBundle::class => ['test' => true, 'dev' => true],
icanhazstring\FoundryBundle\FoundryBundle::class => ['test' => true],
];
Note the environment scoping: this is test/dev infrastructure and should not be loaded in prod. Note also the lowercase vendor segment — PHP namespaces are case-insensitive at runtime but Composer's PSR-4 lookup is not on case-sensitive filesystems, so copy the exact casing from the package's autoload.psr-4 block.
Where to look first. Open the package in vendor/icanhazstring/foundry-bundle and read, in this order:
src/FoundryBundle.php and any src/DependencyInjection/*CompilerPass.php — this is the actual value of the package; it is one or two small classes.src/DependencyInjection/Configuration.php — the config tree, i.e. the knobs you can set.src/DependencyInjection/*Extension::getAlias() — determines your YAML key, and the 0.3.0 class rename may have changed it (default Symfony aliasing would now produce foundry, which is confusingly generic next to Zenstruck's — read it, don't guess).src/Resources/config/*.yaml|php — the service definitions and tags the passes operate on.README.md — the author's stated "common problems".First use case. The realistic scenario this bundle exists for: you are using Foundry factories in a Symfony app, and either (a) your factories aren't being picked up as services / can't be autowired into KernelTestCase subclasses, or (b) state (stories, Faker, proxy objects) bleeds between tests, or (c) the factory services aren't reset by services_resetter between kernel boots. Add the bundle, run an existing KernelTestCase, and see whether the previously failing factory test goes green. If it does, diff APP_ENV=test bin/console debug:container --tag=<the tag the pass adds> before/after to see exactly what changed.
1. Autowired factories in tests. The classic day-to-day win: injecting a concrete factory into a test or a data provider rather than calling MyFactory::new() statically. Once the compiler pass tags factory services, the typical shape is:
final class OrderWorkflowTest extends KernelTestCase
{
use ResetDatabase;
protected function setUp(): void
{
self::bootKernel();
}
public function test_checkout(): void
{
$user = UserFactory::createOne();
$order = OrderFactory::createOne(['user' => $user]);
self::assertSame($user->getId(), $order->getUser()->getId());
}
}
If you inject, use a static::getContainer()->get(...) lookup in setUp() or a small trait; do not constructor-inject into TestCase classes.
2. Reset-between-tests wiring. If the bundle's pass registers Foundry's stateful services as kernel.reset implementers, the pattern is to let KernelTestCase's tearDown() reset the container rather than hand-clearing things:
protected function tearDown(): void
{
parent::tearDown(); // triggers services_resetter -> kernel.reset tags
Story::clear(); // belt-and-braces if the bundle predates reset support
}
Verify the tag is actually applied: APP_ENV=test bin/console debug:container --tag=kernel.reset | grep -i foundry.
3. Test bootstrap config. Put the bundle's settings in config/packages/test/foundry.yaml (or when@test: in the main config), never in config/packages/foundry.yaml, since the bundle is only registered under test. Use the alias from getAlias() — re-derive it after the 0.3.0 rename rather than reusing an old key:
when@test:
foundry: # confirm via Extension::getAlias(), not from an old blog post
# options as documented in Configuration.php
4. Working with Doctrine test isolation. This bundle is a helper, not a replacement for:
Zenstruck\Foundry\Test\ResetDatabase (schema reset between tests),dama/doctrine-test-bundle (transactional wrapping),Doctrine\Bundle\FixturesBundle for non-test seeding.Typical layering: dama wraps each test in a transaction → ResetDatabase handles the first-run schema → Foundry factories create objects inside it. The bundle's passes slot underneath all of this; don't expect it to solve fixture ordering.
5. Integration with PersistentProxyObjectFactory. With proxy objects, identity-map and auto_refresh_proxies settings matter. Assert on ->getId()/->_real() deliberately, and if you see EntityNotFoundException/detached-entity errors, check the bundle's config for whatever it does to proxy refresh before chasing Doctrine.
6. Kernel boot in data providers. If you use @dataProvider with factory data, remember static providers run before setUp(); either self::bootKernel() in the provider or keep providers pure arrays and resolve factories in the test body. The bundle does not change this contract.
Icanhazstring\FoundryBundle\... references (capital I) become icanhazstring\FoundryBundle\.... After the swap, run composer dump-autoload — Composer's classmap/PSR-4 cache will happily keep serving the stale capitalised namespace until you do — then bin/console cache:clear, because compiler passes only execute on cache warmup.FoundryBundle maps to foundry. A generic key like that is a collision waiting to happen alongside zenstruck_foundry, and an old icanhazstring_foundry: key from 0.2.x will now be silently ignored. Confirm with APP_ENV=test bin/console debug:config foundry and APP_ENV=test bin/console config:dump-reference foundry.src/Foundry/FactoriesPass.php implementing CompilerPassInterface, $container->registerForAutoconfiguration(...)->addTag('foundry.factory') (or whatever tag the bundle uses — read the source), add it in your Kernel::build(), and drop the dependency. You get the fix plus future maintainability, and you never have to care what 0.4.0 renames.PassConfig phase (TYPE_BEFORE_OPTIMIZATION, etc.) and priority. If it silently does nothing, pass priority: <higher> or move it in your own build(). Debug with APP_ENV=test bin/console debug:container --tag=foundry.factory --show-private.test only means bin/console commands (which default to dev) can't see its config; run APP_ENV=test bin/console debug:container ... when inspecting.Bundle vs AbstractBundle. If the package extends the old Symfony\Component\HttpKernel\Bundle\Bundle and you're on Symfony 6+, it still works (deprecated) — but if it implements ExtensionInterface, confirm the extension alias matches the YAML key, especially now that the class has been renamed.FrameworkBundle/DoctrineBundle load order. Ensure DoctrineBundle and ZenstruckFoundryBundle are registered before this bundle in bundles.php; bundles.php insertion order influences pass collection order in older Symfony.faker.seed or locale, tests become order-dependent when run in parallel (paratest). Pin the seed and locale explicitly in test config.ResetDatabase/transactional isolation assumes one DB per worker. If you add paratest, you need per-worker databases; nothing in this bundle helps with that, and a stale 2021-era bundle may actively conflict with dama's dynamic DB name switching.composer show icanhazstring/foundry-bundle — confirm 0.3.x and that the old namespace isn't still cached.APP_ENV=test bin/console debug:container --tag=<tag> — is the pass running?APP_ENV=test bin/console debug:container 'icanhazstring\FoundryBundle\' — are services registered under the new namespace?APP_ENV=test bin/console debug:config <alias> — did your YAML key land after the alias change?var_dump()/echo inside the pass and bin/console cache:clear — compiler passes only run on cache warmup, so always clear cache after changing bundles.php, config, or namespaces.static::getContainer() if you must; if the bundle disappears (or you vendor it), you don't want 300 tests tied to its compiler pass tags — or to a namespace that got renamed between point releases.auto_refresh_proxies; re-evaluate whether you need any of this at all. For most projects the correct answer is: use ZenstruckFoundryBundle alone, add ResetDatabase, and skip this package.How can I help you explore Laravel packages today?