Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Phpdotenv Laravel Package

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.

View on GitHub
Deep Wiki
Context7

Getting Started

In a Laravel app you almost never touch this package directly — Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables already calls Dotenv::create(...) for you. So the first thing to look at is not the package, but:

  1. .env.example in the repo root (the contract of required keys).
  2. .env on your machine (copy of the example; must be in .gitignore).
  3. config/*.php — the only place env() should ever be called.
  4. vendor/laravel/framework/src/Illuminate/Foundation/Bootstrap/LoadEnvironmentVariables.php — the exact create/load call and the repository configuration Laravel uses.

Minimal bootstrap in a non-Laravel entry point (legacy script, cron job, standalone test bootstrap, package CLI):

require __DIR__ . '/vendor/autoload.php';

$dotenv = Dotenv\Dotenv::createImmutable(__DIR__);
$dotenv->safeLoad();                       // don't explode if .env is absent

$dotenv->required(['DB_HOST', 'DB_DATABASE'])->notEmpty();
$dotenv->required('DB_PORT')->isInteger();

$dsn = sprintf('mysql:host=%s;dbname=%s', $_ENV['DB_HOST'], $_ENV['DB_DATABASE']);

First real-world use case: onboarding a new developer — copy .env.example to .env, run php artisan key:generate, and have the app fail fast with a readable message (DATABASE_DSN is missing) instead of a random TypeError three layers down. That validation block is the single highest-value thing you can add on top of the default Laravel wiring.

Implementation Patterns

Immutable by default, mutable only for local layering. createImmutable() means real environment variables (Docker, Kubernetes, Forge/Vapor secrets, CI) always win over the file. Keep that for production. For local multi-file layering, use mutable reads in order:

Dotenv\Dotenv::createMutable(__DIR__, '.env')->load();
Dotenv\Dotenv::createMutable(__DIR__, '.env.local')->safeLoad();  // dev overrides

Validate the contract at boot, not at use. Treat the fluent assertions as executable documentation of your environment:

$dotenv->required(['APP_KEY', 'DB_CONNECTION'])->notEmpty();
$dotenv->required('APP_DEBUG')->isBoolean();
$dotenv->required('SESSION_DRIVER')->allowedValues(['file', 'redis', 'database']);
$dotenv->required('MAIL_FROM_ADDRESS')->allowedRegexValues('/^.+@.+\..+$/');
$dotenv->ifPresent('QUEUE_WORKERS')->isInteger();   // optional but typed

Parse without loading for tests and assertions. This is the underused gem — it lets you verify .env.example in CI without touching the process environment:

$example = file_get_contents(base_path('.env.example'));
$parsed  = Dotenv\Dotenv::parse($example);            // nested ${VAR} resolved
$this->assertArrayHasKey('APP_KEY', $parsed);

Same API powers test fixtures and config-generation scripts where you want an array, not superglobals.

Custom repositories for constrained environments. Allow-listing is useful when a shared .env contains keys a sub-application must not see, and mixed read/write adapters let you read from $_SERVER (populated by FPM/fastcgi_param) while writing only to $_ENV:

$repository = Dotenv\Repository\RepositoryBuilder::createWithDefaultAdapters()
    ->allowList(['APP_*', 'DB_*'])   // pattern-style allow lists
    ->immutable()
    ->make();

Dotenv\Dotenv::create($repository, __DIR__)->load();

Nesting to keep .env DRY — ${BASE_DIR} style interpolation works, but see the ordering caveat below.

In Laravel, always funnel through config. env() calls live in config/*.php; application code calls config(). This makes php artisan config:cache safe and keeps you deployable.

Testing pattern. Put fixed values in phpunit.xml (<env name="DB_CONNECTION" value="sqlite"/>) so tests never depend on a developer's .env, and use .env.testing (loaded automatically by Laravel when APP_ENV=testing) for anything else.

Gotchas and Tips

  • config:cache breaks env() outside config files. Once cached, .env is not read and env() returns null anywhere but config/. The classic symptom: works locally, returns null in production. Only ever call env() inside config/.
  • Immutability cuts both ways. createImmutable() will not override an existing value — including a stale export DB_HOST=localhost in someone's ~/.zshrc or a leftover FPM fastcgi_param. If a value "won't change", check getenv()/$_SERVER before blaming the file.
  • $_ENV may be empty on shared hosting. If variables_order in php.ini lacks E (or S), superglobals are missing and writes appear to vanish. Check php -i | grep variables_order. This is the #1 "dotenv doesn't work" ticket.
  • safeLoad() is not try/catch. It suppresses only the missing file exception; validation assertions still throw RuntimeException. Decide deliberately whether a missing .env is fatal.
  • Empty is not unset. FOO= yields '', which passes required() but fails notEmpty(). Use notEmpty() for anything you concatenate into a DSN.
  • Booleans are strings. "false" is truthy in PHP. Either validate with isBoolean() or rely on Laravel's env() coercion of true/false/null/empty/(true)/(false). Don't hand-roll (bool) $_ENV['X'].
  • Quoting and comments. Double quotes interpolate ${VAR} and escape sequences; single quotes are literal. A trailing # starts a comment only in the right context — v5.7 tightened these rules (BOM stripping, comments no longer start multiline values, safer single-char export parsing), so pin ^5.7 if you parse anything exotic.
  • Nesting requires declaration order. ${BASE_DIR} must appear before use in the same file; values already in the environment are also resolvable, which is why immutability affects interpolation sources.
  • getenv()/putenv() is not thread-safe — hence createUnsafeImmutable() being explicitly "unsafe". Avoid it under ZTS/Octane-style workloads; the default adapters are enough.
  • Long-running workers (Octane, Swoole) load .env once. Changing it does nothing until restart; never read $_ENV at request time expecting live values.
  • Debugging order: Dotenv\Dotenv::parse(file_get_contents('.env')) → $_ENV → $_SERVER → getenv() → config(). Whichever layer first differs tells you whether the problem is parsing, adapters, immutability, or config caching.
  • Security hygiene: .env in .gitignore, .env.example committed and kept in sync (add a CI diff-keys check), real secrets injected at deploy time via your platform's secret store rather than shipped in the image, and never dump() the parsed array into logs or exception handlers.
  • Extension point worth knowing: RepositoryBuilder (adapters + allowList + immutable) and the adapter interface are the supported seams; the parser itself is not intended for subclassing.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
mehedimi/laravel-amp-mail
happones/laravel-vue-i18n-generator
jwerd/inertia-table
siberfx/flysystem-wasabi
hollyit/token-replace
ippanelcom/laravel-sdk
adachsoft/changelog-linter
adachsoft/collection
payerurl/telegrambot-crypto-payment-instant-payout
dskripchenko/laravel-hybrid-search
riorizkyrainey/lara-ced
mehedimi/static-vel
zharp/zharp
nonsapiens/bigquery-model-sync
lgu-bayambang-ict-office/lgu-laravel-development-standards
andydefer/laravel-support
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel