Download the PHP package treast/behat-fixture-bridge without Composer
On this page you can find all versions of the php package treast/behat-fixture-bridge. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.
Informations about the package behat-fixture-bridge
Behat Fixture Bridge
A bridge between your fixture factories and your Behat scenarios. Annotate a factory, get a stable symbolic name (
@[book:42]), use it in your Gherkin steps. No seed, no hardcoded IDs, no breaking existing tests.
The problem
You generate realistic fixtures with Faker. Hundreds of entities get random IDs and random values on every run. Your Behat scenarios need to reference one specific entity to visit its page, edit its title, or assert it appears in a list. Hardcoded IDs break as soon as the fixture order changes. Seeding Faker is fragile the moment you add a property to an entity.
Behat Fixture Bridge solves this by giving each factory the ability to register the entities it creates under a stable, human-readable name:
These names are written to a JSON file that Behat reads. Your scenarios then use
@[book:42] anywhere a value is expected:
The ID and properties are resolved at runtime, from the registry.
Installation
1. Require the package
2. Register the bundle (dev and test only)
In config/bundles.php:
The bundle is never loaded in production. If someone accidentally enables it
in prod, no harm is done — the listeners are idle when no entity matches the
registered metadata.
3. Configure the bundle
Create config/packages/test/behat_fixture_bridge.yaml:
| Option | Default | Purpose |
|---|---|---|
file |
%kernel.project_dir%/var/fixtures/registry.json |
Where the registry is written. |
auto_flush_on_terminate |
true |
Write the file after each Doctrine flush and on kernel/console shutdown. |
purge_on_fixtures_load |
true |
Wipe the registry before doctrine:fixtures:load (skipped if --append). |
Add the registry file to .gitignore:
4. Enable the Behat context
In behat.yml (or behat.yaml):
The context is public in the container, so friends-of-behat/symfony-extension
will inject the registry automatically. No bootstrapping needed.
Quick start
Annotate a factory
If you use Foundry, add the attribute on top of the factory class:
Load your fixtures
Your fixtures run as usual. Every time a Book is persisted, the listener
registers it in the registry:
Use it in a scenario
That's it. The @[book:1] placeholder is resolved to 1 before the step runs.
How placeholders work
The context exposes a single @Transform that recognises two syntaxes and
substitutes them anywhere they appear.
ID: @[name]
Resolves to the entity's identifier. If the entity has a composite identifier, the transform returns an array.
Property: @[name][property]
Resolves to a registered property.
Inline substitution
Placeholders work inside a larger string, not only as standalone arguments.
This is the main advantage over the older @name syntax: [ and ] are
impossible in emails or social handles, so there is no ambiguity:
In tables
Transforms do not run on TableNode cells. Use the dedicated step:
parent becomes 1, title becomes Les Misérables (original).
The attribute in detail
name (required)
The prefix of the final key. Can contain dots, colons, and dashes:
identifier (default: 'id')
How the suffix after : is built. Four forms:
Property or getter on the entity:
Static method on the factory:
Composite identifier:
No suffix:
if (optional)
Name of a static method on the factory that filters which entities get registered:
Only featured books are registered. Non-featured books are persisted as usual but do not appear in the registry.
properties (optional)
Extra properties exposed via @[name][property]. Two syntaxes:
'@authorName' calls a static method on the factory:
Storage accepts scalars, DateTimeInterface, BackedEnum, Stringable, and
arrays of these. Anything else throws a clear exception at registration time.
Supported return types for identifiers and properties
The registry is a JSON file. Only serialisable values are accepted.
| Type | Stored as |
|---|---|
string, int, float, bool |
as-is (bool becomes 1/0 in identifiers) |
null |
null (property) or omitted (identifier) |
DateTimeInterface |
ISO 8601 string |
BackedEnum |
its ->value |
Stringable |
(string) $value |
| Array of the above | array (properties) or --joined string (identifier) |
Objects are rejected. If you need to expose a relation, add a factory method that returns a scalar:
Where the registry lives
By default: var/fixtures/registry.json. It looks like this:
You can read it, diff it in CI, commit it if you want a snapshot, or delete it
freely — it is regenerated on every doctrine:fixtures:load.
Lifecycle
| Moment | What happens |
|---|---|
doctrine:fixtures:load starts (no --append) |
The registry is wiped and the file is deleted. |
Each persist() of a matching entity |
An entry is registered in memory. |
| After each Doctrine flush and on shutdown | The file is written to disk. |
| Behat scenario runs | The context reads the file lazily (first access). |
If you need to inspect the registry during a scenario from a custom step, inject
FixtureRegistryInterface:
Manual registration (without the attribute)
Some cases are hard to express with an attribute — a key built at runtime, or several keys for the same entity. Use the trait instead:
The class must be a Symfony service with autowiring enabled, so the trait's
setFixtureRegistry() is called automatically.
Inheritance: Art and its subclasses
When an entity has subclasses (Book extending Art), use
one factory per concrete class, an abstract factory for shared fields, and two
attributes per concrete factory:
Now @[book:1] targets the specific type, while @[art:1] works regardless
of the concrete class.
A full example
Fixtures
Result: book:1, book:2, and 150 more. You only ever reference book:1 and
book:2 in scenarios — the others are noise that makes the UI realistic.
Feature
No hardcoded ID, no seed, no brittle assertion.
Troubleshooting
Class "...FixtureBridgeContext" not found
Composer does not know the class yet:
Interface "...FixtureRegistryInterface" not found at container compile time
The bundle is not registered, or is registered in prod only. Check
config/bundles.php — it must include BehatFixtureBridgeBundle::class for
both dev and test.
Typed property ... must not be accessed before initialization
The context still has a setFixtureRegistry() setter. In a Symfony + FoB setup,
inject by constructor. Remove the setter and the interface; let the container
pass the registry.
@[book:1] is not substituted and appears in the URL
The context is not registered in behat.yml for the current suite. Add it
under contexts. Verify with:
The @Transform from FixtureBridgeContext should appear in the list.
Property "title" is not registered for "book:1"
The property is missing from the properties argument of
#[AsFixtureBridge]. Add it:
Then reload fixtures so the registry picks up the new properties.
Requirements
| Dependency | Version |
|---|---|
| PHP | 8.4+ |
| Behat | 3.13+ |
| Doctrine ORM | 2.14+ or 3.0+ |
| Symfony | 6.x, 7.x, 8.x |
Optional but recommended:
friends-of-behat/symfony-extension(auto-instantiates the context via the container)zenstruck/foundry(attribute detection supports both v1 and v2)
Contributing
composer ci runs Rector (dry-run), PHP CS Fixer (dry-run), and PHPUnit. It
mirrors exactly what GitHub Actions does, so a green local run guarantees a
green CI.
See CONTRIBUTING.md for details.
License
MIT. See LICENSE.
All versions of behat-fixture-bridge with dependencies
behat/behat Version ^3.13
doctrine/orm Version ^2.14 || ^3.0
symfony/config Version ^6.0 || ^7.0 || ^8.0
symfony/dependency-injection Version ^6.0 || ^7.0 || ^8.0
symfony/http-kernel Version ^6.0 || ^7.0 || ^8.0
symfony/console Version ^6.0 || ^7.0 || ^8.0