brianium/habitat
Habitat is a lightweight PHP library for managing environment variables and app configuration. It offers a simple API for reading values from .env files and system environment variables, with defaults and fallbacks, helping keep configuration out of code across development and production environment...
Install
composer require brianium/habitat
There are no framework bindings, no service provider, no config file to publish — it's a small standalone library, so installation is the whole setup.
Where to look first
src/ — the package is tiny; read the main Habitat class top to bottom before writing anything. The README is essentially the only documentation, and for a package this old it lags the code in places.getInstance() entry point — confirm whether you're dealing with a singleton, a static facade, or both.$_SERVER, config files) — this tells you the resolution order and precedence, which is the single most important thing to understand.Minimal first use case
The canonical job is: resolve one environment name at bootstrap, then read values through that object instead of touching $_SERVER, $_ENV, getenv(), or hand-rolled if (APP_ENV === 'prod') checks scattered across the codebase.
use Habitat\Habitat;
$habitat = Habitat::getInstance();
$habitat->setEnvironment(getenv('APP_ENV') ?: 'production');
$debug = $habitat->get('debug');
Start by replacing one direct getenv()/$_SERVER lookup in your bootstrap with a Habitat read. If that works, expand outward. Don't rewrite every environment read on day one — this package is a thin abstraction, and you want to confirm its precedence rules match your assumptions before you depend on it.
Verify PHP compatibility early: this is a PHP 5.3/5.4-era package and won't have been tested against modern PHP. Run your test suite on the target PHP version before committing to it.
Bootstrap once, read everywhere
Configure the environment in your front controller (or Laravel service provider / Symfony kernel boot) and never call setEnvironment() again at runtime:
// bootstrap.php
$habitat = Habitat::getInstance();
$habitat->setEnvironment(getenv('APP_ENV') ?: 'development');
Wrap it in your own accessor
Because it's a singleton with global state, the highest-leverage move is to hide it behind a small class of your own:
final class Env
{
public static function get(string $key, $default = null)
{
$value = Habitat::getInstance()->get($key);
return $value ?? $default;
}
}
This gives you one place to add defaults, casting, and logging — and one place to swap the implementation if the package goes stale (which, with 0 dependents and 44 stars, is a real risk).
Per-environment configuration
The natural workflow is a directory per environment, with the same filenames in each, so switching environments swaps the whole value set:
config/
development/
database.php
app.php
production/
database.php
app.php
Then in tests you point the root path at a fixture directory and flip the environment name — no code changes, no mocking framework required.
Test setup / teardown
The most valuable pattern for a package like this is test isolation. In setUp() set the environment to testing and seed the values your test needs; in tearDown() reset the singleton (re-instantiate it, or use reflection if there's no reset()), otherwise state leaks between test cases and produces order-dependent failures.
protected function setUp(): void
{
$this->habitat = Habitat::getInstance();
$this->habitat->setEnvironment('testing');
}
protected function tearDown(): void
{
// reset the singleton here — check the source for the supported way
}
CLI and queue workers
Set the environment explicitly at the top of console entry points and queue workers. Long-running processes are exactly where a stale global environment bites you: a worker that boots as production and is later expected to behave like staging won't reload on its own.
Framework integration
There's nothing to integrate with — you write the glue. In Laravel, bind the instance as a singleton in a service provider and inject your Env wrapper. In a legacy app, a single require in the bootstrap file is enough. Don't let the static calls spread into domain code; confine them to infrastructure.
The singleton is the main pitfall. Global mutable state means ordering matters: anything that reads a value before setEnvironment() runs will get the old/default environment. Audit your include order, and set the environment as early as your bootstrap allows.
Know the precedence order. If the package consults more than one source (e.g. $_SERVER, $_ENV, getenv(), then files), confirm which wins. Two classic traps: variables_order in php.ini determines whether $_ENV is populated at all (many distros ship with it disabled), and getenv() may or may not see values set via putenv() depending on SAPI (getenv() is notably unreliable under some FPM/threaded setups).
Missing keys and falsy values. Check whether a missing key returns null, false, or throws. null coalescing hides a genuine false/'0'/'' value, so use an explicit has() check if one exists rather than ?? alone.
Environment file pathing. If it loads files, relative paths resolve against the current working directory, which differs between web, CLI, cron, and test runners. Always pass an absolute root path (__DIR__ . '/config').
Case sensitivity and key format. Whether 'DB_HOST' and 'db_host' are the same key — and whether dot notation ('database.host') means nesting or is a literal key — is worth a two-minute test each. Guessing here causes silent nulls.
Maintenance risk. Zero dependents, old release history, and no CI signal on modern PHP. Pin the version in composer.json, keep the wrapper class above so replacement is a one-file change, and if you hit a deprecation on PHP 8 (dynamic properties, implicit nullable params, curly-brace string offsets in the internals), vendor the package and patch rather than waiting on upstream.
Debugging. When a lookup misbehaves, dump the whole resolved environment rather than the single key — you want to see which source answered. Add temporary logging in your Env wrapper around the Habitat call; that's cheaper than instrumenting the package's internals.
Don't put secrets in version-controlled environment files. Habitat tells you which environment you're in; it's not a secret manager. Feed credentials in via real environment variables and keep the config files to non-sensitive defaults.
Extension points are thin. There's no plugin system worth relying on. If you need extra behavior — casting, required-key validation, fallback chains — build it in your wrapper, not by subclassing the internals, which are unlikely to stay stable across versions.
Treat it as a bootstrap convenience, not an architecture. The package solves "give me a dependable answer to what environment am I in and what are its values." Keep it at the edges of the application; the moment it reaches into business logic you've traded a clear getenv() for an indirect one.
How can I help you explore Laravel packages today?