fervo/environment-bundle
fervo/environment-bundle simplifies environment configuration for PHP applications, helping you define and manage settings for local, staging, and production environments while keeping environment-specific values organized and easy to switch.
Install (with a serious caveat). 0.2.0 is the first release since August 2016. It is still a low-traffic, effectively abandoned repo — read the release notes before you assume anything changed: the entire delta is parity with an old symfony/symfony PR, not modernisation. There is no signal in 0.2.0 of PHP 8 support, a Symfony 4+ Flex recipe, or updated framework constraints. Open composer.json first and read the require block — you will almost certainly still find symfony/symfony / symfony/framework-bundle pinned to the ~2.7–~3.x range and PHP ~5.5/~7.0. On a modern Symfony (5.x/6.x/7.x) project, composer require fervo/environment-bundle will fail on platform requirements. If you are evaluating it for a legacy app, that is fine; if not, stop here and use Symfony's built-in %env()% + Dotenv (see Gotchas).
If you are on a compatible stack:
composer require fervo/environment-bundle
Then register the bundle the way your Symfony version expects:
// Symfony 2/3: app/AppKernel.php
public function registerBundles()
{
$bundles = [ /* ... */ ];
if (in_array($this->getEnvironment(), ['dev', 'test'], true)) {
$bundles[] = new Fervo\EnvironmentBundle\FervoEnvironmentBundle();
}
return $bundles;
}
// Symfony 4+: config/bundles.php
return [
// ...
Fervo\EnvironmentBundle\FervoEnvironmentBundle::class => ['all' => true],
];
Where to look first. The README is still thin. Spend your first ten minutes in source, in this order:
composer.json — version constraints and the autoload.psr-4 namespace. This tells you immediately whether it can coexist with your Symfony — 0.2.0 does not appear to have changed that story.*Bundle class — usually trivial, but it often names the DI extension alias, and 0.2.0 removed the trait, so any shared state that used to live there is now elsewhere.DependencyInjection/*Extension.php — this is where the real behaviour lives: what parameters it sets, what services it registers, what configuration tree it defines.Resources/config/services.yml (or .xml) — the actual service IDs you can inject.Tests/ — if it exists, it is the only documentation you will get. 0.2.0 does not advertise test additions.First use case. The realistic value proposition for a bundle named "Environment" is: resolve environment-specific values (hostnames, feature flags, debug toggles) without duplicating config_dev.yml / config_prod.yml. Try to get one concrete thing working end-to-end — e.g. reading one env var with a default via the bundle's env() helper — rather than adopting the whole bundle. If you cannot demonstrate that in an hour, the bundle is not earning its dependency.
Inject, don't reach. Whatever the bundle exposes (a parameter, a service, or an environment resolver), consume it through constructor injection in your own service and hide it behind an interface:
interface EnvironmentResolver
{
public function name(): string;
public function is(string $env): bool;
}
final class BundleEnvironmentResolver implements EnvironmentResolver
{
public function __construct(private $inner) {}
public function name(): string { return $this->inner->getName(); }
public function is(string $env): bool { return $this->inner->getName() === $env; }
}
This costs ten minutes and saves you an afternoon when you inevitably have to rip the bundle out — and 0.2.0 is a reminder that even dormant bundles make breaking changes.
Use the second argument of env(). 0.2.0 adds a default value parameter. Prefer it over try/catch:
$endpoint = env('PAYMENT_ENDPOINT', 'https://sandbox.example.com');
If you call env() with no default and the variable is missing, 0.2.0 raises InvalidArgumentException. Design your call sites so that the default is the normal path and the exception is genuinely exceptional.
Conditional bundle registration. The classic pattern this bundle era encouraged was registering bundles only in certain environments in AppKernel::registerBundles(). Keep that logic in one method, not scattered — and prefer a plain array of environment-name keys over in_array() chains.
Layer it under your config, not over it. Use the bundle to supply values into parameters that your existing config/packages/*.yaml consumes, rather than letting it own your configuration tree:
# config/services.yaml
services:
App\Service\PaymentGateway:
arguments:
$endpoint: '%fervo_environment.payment_endpoint%' # name is illustrative — verify the real parameter key
Run bin/console debug:container --parameters to find out what keys it actually sets before wiring anything.
Keep per-environment config in the bundle's namespace. If it provides a config tree, put those blocks at the top of each config_{env}.yml so reviewers can see environment drift in one diff, rather than buried mid-file.
Integration tip for legacy apps. If you are on Symfony 2.8/3.4, this bundle slots in alongside parameters.yml cleanly. Do not mix three mechanisms (bundle config + parameters.yml + getenv()) — pick the bundle or parameters, and route everything through one accessor.
Isolate the dependency. If you must use it, add a single adapter class and reference the bundle nowhere else. Then your upgrade path is "delete one file and one composer line."
0.2.0 is a breaking change for existing users. The trait is gone. If you are upgrading from 0.1.x, search your codebase for any use Fervo\EnvironmentBundle\…Trait; / use …Trait alias and any $this->env(…) calls that depended on the trait's state. Replace them with a service injection or a static helper — you cannot rely on the trait's ambient state anymore.
The env() contract changed. Three things now matter:
env('FOO') with no default and a missing FOO now throws InvalidArgumentException instead of whatever 0.1.x returned.InvalidArgumentException, the PR throws a subclass of that". If you are porting code that caught the Symfony PR's specific exception class, your catch block will not match — catch InvalidArgumentException broadly, or you will get an uncaught exception in production.Do not chase the Symfony PR reference. The release notes say 0.2.0 was updated "to match (a few of) the changes in the PR against symfony/symfony." That PR was the %env()% processors work from the Symfony 3.2/3.3 era (2016). In 0.2.0 the bundle is still playing catch-up to Symfony internals from eight years ago. That is the strongest possible signal that there is no intention to track modern Symfony. Do not expect PHP 8, attributes, or Flex.
It is effectively abandoned. Low star count, near-zero dependents, and a delta that is just a stale PR sync. That means: no PHP 8 support, no Symfony 4+ Flex recipe, no security patches, no answers on Stack Overflow. Treat it as vendored code you now own.
Composer will fight you — don't fight back. The tempting fix is composer require --ignore-platform-reqs or --with-all-dependencies. On a 2016-era bundle (0.2.0 does not appear to relax this) this produces a dependency graph that composer install in CI cannot reproduce. If you genuinely need it, fork it, bump the constraints yourself, and require the fork via a vcs repository entry.
Name collision with kernel.environment. Symfony already has a first-class notion of environment (%kernel.environment%, kernel.environment parameter). A bundle called "Environment" will confuse new team members about which one is authoritative. Document, in the README of your app, exactly which one to use and when.
Naming and DI alias guessing. Extension aliases in this era were often inconsistent (fervo_environment vs environment vs fervo_environment_bundle). Confirm with:
bin/console debug:config fervo_environment # will error if the alias differs
bin/console debug:container --tag=container.hot_path
bin/console debug:container | grep -i fervo
If debug:config says the extension is not loaded, the bundle's getContainerExtension() is returning null — check the *Bundle class for a getContainerExtension() override or an Extension class that doesn't follow the namespace convention.
Caching behaviour is the likely bug source. Environment-derived values baked into container parameters are frozen at cache:warmup. In dev this looks like "my change did nothing"; in prod it looks like "the wrong environment value shipped." This is more important in 0.2.0 because the exception-on-missing behaviour means a missing variable during warmup can fail the whole cache warm — and because the exception is thrown from the container build, the stack trace will point at your cache warmup, not at the call site. Always test with bin/console cache:clear --env=prod after changing anything environment-related, and never store secrets in bundle parameters — they land in var/cache as plain PHP arrays.
No tests, no docs, no issue tracker activity. Write your own smoke test the day you adopt it:
public function testBundleServiceIsWired(): void
{
$container = self::getContainer();
self::assertTrue($container->has('the.service.id.you.actually.use'));
}
Add one more covering the new behaviour:
public function testEnvHelperUsesDefaultForMissingVariable(): void
{
self::assertSame('fallback', env('THIS_VAR_DEFINITELY_DOES_NOT_EXIST', 'fallback'));
}
public function testEnvHelperThrowsWhenMissingWithoutDefault(): void
{
$this->expectException(\InvalidArgumentException::class);
env('THIS_VAR_DEFINITELY_DOES_NOT_EXIST');
}
Those two tests are your regression alarm for a library nobody else is watching.
The honest recommendation (stronger than before). On Symfony 3.4+ this bundle is now strictly worse than core Symfony. %env(default:fallback:BAR)% and %env(bool:BAR)% provide the same default-and-throw semantics the bundle just gained, are maintained, are documented, and are understood by every Symfony developer you will hire. 0.2.0 does not change that — it confirms the bundle is reimplementing, slowly, what core has shipped for years. If you are on 0.1.x, upgrade to 0.2.0 only if you cannot migrate yet (trait removal is breaking, exception change is breaking), and immediately schedule the migration: delete the bundle, replace its parameters with %env()% resolutions, and keep the same service interface so no calling code changes.
How can I help you explore Laravel packages today?