Symfony applications
The Symfony bridge supplies Symfony services and built-in Greenlight harness services to test constructors.
The bridge boots the application kernel once for each worker. Register the plugin to activate the bridge. The bridge uses the Symfony packages that the application provides. Greenlight does not declare a runtime dependency on Symfony.
Setup
Register the plugin in greenlight.php with your kernel class:
use Greenlight\Config\GreenlightConfig;
use Greenlight\Symfony\SymfonyPlugin;
return GreenlightConfig::create()
->paths(['tests'])
->plugins(static fn(): SymfonyPlugin => new SymfonyPlugin(App\Kernel::class, env: 'test', debug: false));
Use a closure when the kernel needs custom construction:
new SymfonyPlugin(static fn(): KernelInterface => new App\Kernel('test', false));
The kernel boots when a test first requests a container service. It remains active for the worker lifetime. A worker does not boot Symfony when its tests do not use the container.
Greenlight tests the bridge with symfony/framework-bundle 6.4, 7.x, and 8.x.
At boot, the bridge requires the kernel container to expose
test.service_container. When service resets are active, it also requires
services_resetter. A standard FrameworkBundle test environment provides the
test container when framework.test is active.
If a required container service is absent, the bridge reports a configuration error with a correction. It does not use weaker isolation.
Container services
Declare the dependency by type:
final class RegistrationTest
{
public function __construct(
private readonly UserRepository $users,
private readonly RegistrationHandler $handler,
) {}
}
Without an explicit service source, Greenlight first resolves constructor
parameters from its harness. It then uses the Symfony container. Thus,
Doubles, TestChannel, and provider services take precedence over container
services.
When neither side can resolve a type, the test fails and reports both misses.
Bridge setup and service-resolution failures throw ServiceResolutionFailed.
Concrete Symfony bridge exceptions are internal.
The normal Symfony test-container rules still apply. The container must reference a private service to retain it during compilation. The Symfony compiler can remove an unused service. Greenlight cannot inject a removed service.
Services without a usable type
Type alone cannot select some services. Examples include string-ID-only
services, interfaces with multiple implementations, and decorated services.
Use #[Service] to name the service explicitly:
use Greenlight\Harness\Service;
public function __construct(
#[Service('mailer.transports.async')] private readonly TransportInterface $transport,
) {}
Greenlight still checks the parameter type. If the named service is not an instance of the declared type, the test fails and does not receive the object.
Select a service source
Pass source: 'app' to SymfonyPlugin to name this plugin instance. Use
#[Service(source: 'app')] to request a service by type from this source.
Use #[Service('mailer.transports.async', source: 'app')] to select an explicit
ID in its container.
An explicit source takes precedence over global harness services. A missing
service fails without a request to another source. Use the same source
attribute to select this plugin’s KernelInterface harness service. See
service sources for naming and resolution rules.
The kernel itself
Greenlight supplies KernelInterface as a per-worker harness service. Tests can
use it to inspect boot parameters or the container directly:
public function __construct(private readonly KernelInterface $kernel) {}
State between tests
The bridge keeps the kernel active between tests.
After each test, the bridge calls Symfony’s services_resetter, the same reset
mechanism Symfony uses between requests. The resetter resets services with the
kernel.reset tag. This includes services that Symfony configures from
ResetInterface. It also includes common Symfony services such as Doctrine’s
ManagerRegistry, cache pools, and the profiler. Implement ResetInterface for
each stateful service that must keep tests isolated.
The bridge captures and checks the resetter when the kernel boots. If service resets are active without a container resetter, each test that requests the kernel or a container service has an error. Tests that use only other harness services do not boot the kernel.
For a container that has no stateful services, pass resetBetweenTests: false
to the plugin. This value disables the resetter requirement. Do not use this
value with services that keep state. Tests on the same worker will share those
service instances.
The bridge does not isolate databases or other external services.
Parallel resources
Workers run tests at the same time. Split shared external resources for each worker. Alternatively, protect them with a concurrency limit.
Greenlight sets GREENLIGHT_CHANNEL in every worker process. Its value is a
stable number from 1 through the worker count. Within one run, concurrent tests
use different channels. Use it in Symfony configuration to name resources:
# config/packages/test/doctrine.yaml
doctrine:
dbal:
dbname: 'app_test_%env(default:fallback_channel:GREENLIGHT_CHANNEL)%'
parameters:
fallback_channel: '1'
The default: processor reads the fallback_channel container parameter when
GREENLIGHT_CHANNEL is absent. See the Symfony
environment variable processors.
The same pattern works for cache directories, upload paths, message transport names, and similar resources.
Separate runs and CI shards reuse channel numbers. Add a resource prefix for each concurrent run that uses the same external service.
The application must create and migrate databases for each channel. Use a loop in the test bootstrap, a Makefile target, or another project-level setup step. Channel numbers remain stable after a worker crash. Thus, these schemas can remain for the complete test run.
If you cannot split a service per channel, mark the classes that use it with
#[RequiresResource]. Configure its safe concurrency:
#[RequiresResource('payments-sandbox')]
final class PaymentGatewayTest { ... }
return GreenlightConfig::create()
->resourceLimit('payments-sandbox', 2);
The limit controls how many assignments that require this resource can run. It does not choose a service instance, and it does not coordinate another Greenlight process or CI shard. See configuration for the complete resource rules.
Doubles and the container
The bridge does not replace container services with doubles. If a test needs a
doubled collaborator, get the double through Doubles. Then construct the test
subject directly. Greenlight then controls the double lifecycle and
verification.
Non-goals
The current bridge does not cover:
- HTTP request and response tests with
KernelBrowser - transaction rollback isolation
- dotenv file load
- kernel auto-discovery
- database creation or migration tools
- Messenger assertions