vlucas/phpdotenv
PHP dotenv loads environment variables from a .env file into getenv(), $_ENV and $_SERVER, keeping credentials and per-environment config out of code. A PHP port of Ruby dotenv, installable via Composer and works with Apache, Nginx, CLI and PHP's built-in server.
.env for developer machines, platform-injected env vars for staging/prod, and a secrets manager (Vault, AWS Secrets Manager, SSM) for regulated production. Adopting it forces the team to actually decide where each tier lives.${VAR} nesting, immutability, and typed validation are all security-adjacent edge cases you would get wrong. The v5.7.0 changelog is a good argument on its own: memory-safety crash on invalid UTF-8 variable names, null-byte-safe putenv adapter, quadratic parsing removal..env.example committed + .env gitignored turns "what config does this app need?" into a machine-checkable contract. That is a concrete reduction in first-week support tickets and "works on my machine" Slack threads.php_value flags, no virtual-host edits, no out-of-repo files. This directly supports a "one artifact, many environments" deployment decision.required(), notEmpty(), isInteger(), isBoolean(), allowedValues(), allowedRegexValues(), and the ifPresent() variants let you ship a startup self-check. Turn "500 error three hours into a request" into "container refuses to start with a clear message." That is a reliability decision, not a library choice..env plus committed .env.example is a demonstrable "no credentials in version control" control for security reviews and compliance questionnaires. Note the boundary: it prevents accidental commits, it does not distribute production secrets.allowList(['FOO','BAR']) supports minimal-privilege loading per module; Dotenv::parse("FOO=Bar\nBAZ=\"Hello ${FOO}\"") gives you config-as-string for tests, importers, and CLI tooling without filesystem coupling.createImmutable (never overwrite an already-set variable) versus createMutable, plus safeLoad() for absent files, means "prod env always wins over a stray .env" becomes a policy you configure rather than a hope.Adopt when:
?? defaults and null checks through the codebase.Look elsewhere, or pair with something else, when:
.env files are plaintext and unencrypted; the maintainers themselves discourage getenv()/putenv() as thread-unsafe and default you to createImmutable. Treat this as dev/staging infrastructure and pair it with a real secrets backend in prod..env there is redundant at best and a footgun at worst — a committed .env in an artifact is a breach.variables_order omits E/S. Superglobals can be disabled, and the workaround is the discouraged createUnsafeImmutable/PutenvAdapter path. Verify the target infrastructure before committing.Risk notes to verify before sign-off: BSD-3-Clause is permissive and commercially safe, no license cost, and the release cadence is healthy with a 2026-10-25 release and an active, sponsored maintainer. The concentration risk is a single primary maintainer — mitigated in practice by low complexity and the huge install base, but worth recording. Also treat the "0 dependents" figure in our tooling as a data artifact, not evidence: Laravel and most PHP skeletons ship this transitively, so its real blast radius is effectively every PHP app you own. Confirm that transitive path during the supply-chain review rather than relying on our dependents count.
For executives (30 seconds): We are adopting a free, BSD-licensed, 13.5k-star PHP package that keeps passwords and API keys out of our source code. It is the de facto standard in the PHP ecosystem and already ships inside Laravel, so we are formalizing something we effectively depend on already. Cost: zero license, roughly one line of boot code and a .env.example template per repo. Benefit: removes an entire class of credential-leak incidents, cuts new-developer setup to a copy-and-fill step, and makes misconfigured deployments fail loudly at startup instead of silently in production. Risk: low — mature, actively released, permissively licensed; the only notable concentration is a single primary maintainer, offset by its ubiquity.
For engineering leads: Drop-in via Composer. Load once at bootstrap with createImmutable in production so platform-injected values always win, safeLoad() where a missing file should not be fatal, and createMutable only where local override is genuinely intended. Declare required keys once with required([...])->notEmpty() and use isInteger/isBoolean/allowedValues for anything that becomes a driver or switch — this replaces scattered null-coalescing and gives you a hard startup failure with a readable message. Use allowList() for minimally-scoped workers and Dotenv::parse() to keep tests filesystem-free. v5.7.0 adds PHP 8.6 support and removes quadratic value parsing, so it also unblocks our runtime upgrade.
For security and compliance: The pattern satisfies "no credentials in version control" as a demonstrable control — .env gitignored, .env.example committed as the requirement contract, secrets distributed out-of-band. Be explicit about the boundary in the control description: this is not a secrets manager, values are plaintext on disk, and there is no rotation or audit trail. Production must receive secrets from the platform or a managed secrets service. Recommend documenting that split in the security policy before rollout so the tool is not oversold.
The ask: approve as the standard for local and staging configuration, add .env.example to the repository template, require boot-time assertions for critical keys, and open a separate decision record for the production secret-injection mechanism.
How can I help you explore Laravel packages today?