symfony/dotenv
Symfony Dotenv reads .env files and exposes variables via $_ENV/$_SERVER. Load one or multiple files, optionally overwrite existing values, or use loadEnv() to handle .env.local and environment-specific variants for local development and deployment.
Installation:
composer require symfony/dotenv
Add to composer.json under require:
"symfony/dotenv": "^8.0"
Basic Usage:
Create a .env file in your project root:
APP_NAME=MyLaravelApp
DB_HOST=localhost
Load it in your Laravel application (e.g., in bootstrap/app.php or a service provider):
use Symfony\Component\Dotenv\Dotenv;
$dotenv = new Dotenv();
$dotenv->load(__DIR__.'/../.env');
Access Variables:
$appName = $_ENV['APP_NAME'] ?? $_SERVER['APP_NAME'] ?? null;
$dbHost = $_ENV['DB_HOST'] ?? $_SERVER['DB_HOST'] ?? 'default';
Leverage Laravel’s Built-in Support:
Laravel already integrates symfony/dotenv via Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables. No additional code is needed in most cases—just ensure .env exists.
Environment Loading Hierarchy:
loadEnv() for automatic loading of .env, .env.local, and .env.{APP_ENV}.local:
$dotenv->loadEnv(__DIR__.'/../.env');
overload() for explicit file prioritization:
$dotenv->overload(__DIR__.'/../.env.production');
Variable Expansion:
Reference other variables in .env:
APP_URL=https://${APP_DOMAIN}
APP_DOMAIN=example.com
Symfony resolves ${APP_DOMAIN} during loading.
Conditional Loading:
Load environment files based on runtime conditions (e.g., APP_ENV):
$envFile = __DIR__.'/../.env.'.($_ENV['APP_ENV'] ?? 'local');
$dotenv->load($envFile);
Integration with Laravel:
boot() method:
public function boot(): void
{
$dotenv = new Dotenv();
$dotenv->load(__DIR__.'/../../.env');
}
use Symfony\Component\Dotenv\Dotenv;
protected function handle(): void
{
$dotenv = new Dotenv();
$dotenv->load(__DIR__.'/../../.env');
// Command logic...
}
Testing:
Use overload() to inject test-specific environments:
$dotenv->overload(__DIR__.'/../../.env.testing');
Custom Paths:
Override the default .env path via SYMFONY_DOTENV_PATH:
SYMFONY_DOTENV_PATH=/custom/path/to/.env
Dynamic File Generation:
Generate .env files programmatically (e.g., from a database or API):
$envContent = "DB_PASSWORD=".password_generate();
file_put_contents(__DIR__.'/../.env', $envContent);
$dotenv->load(__DIR__.'/../.env');
Validation:
Combine with vlucas/phpdotenv or custom validation to enforce variable formats:
if (!filter_var($_ENV['APP_URL'], FILTER_VALIDATE_URL)) {
throw new \RuntimeException('Invalid APP_URL in .env');
}
Security:
phpdotenv’s .env.local for secrets (excluded from Git).$dbPassword = $_ENV['DB_PASSWORD'] ?? '';
\Log::debug("DB_PASSWORD set (masked)");
Variable Corruption:
.env multiple times corrupts variables (fixed in v8.0.9+).load() once or overload() for updates.$_ENV/$_SERVER before reloading:
$_ENV = [];
$_SERVER = array_merge($_SERVER, ['APP_ENV' => 'local']);
Self-Referencing Variables:
A=${B}, B=${A}) cause infinite loops.overload() to prioritize files or restructure variables.Escaped Dollars:
$ in values (e.g., DB_PASSWORD=pass$word) may break expansion.\\:
DB_PASSWORD=pass\\$word
BOM (Byte Order Mark):
.env files with BOM (common in Windows) throw errors.Case Sensitivity:
$_ENV is case-insensitive on Linux but case-sensitive on Windows.APP_ENV instead of app_env).NUL Bytes:
str_replace("\0", '', $value) before writing to .env.Inspect Loaded Variables:
var_dump($_ENV, $_SERVER);
Or use Symfony’s debug command:
php bin/console debug:dotenv
Check File Permissions:
Ensure .env is readable by the web server (e.g., chmod 644 .env).
Validate Syntax:
Use an online .env validator or test with:
$dotenv = new Dotenv();
try {
$dotenv->load(__DIR__.'/../.env');
} catch (\Exception $e) {
echo "Error: ".$e->getMessage();
}
CI/CD Pitfalls:
.env to .gitignore but use secrets:
env:
DB_PASSWORD: ${{ secrets.DB_PASSWORD }}
.env files as volumes or use env_file in docker-compose.yml.Custom Parsers:
Extend Symfony\Component\Dotenv\Parser to support custom formats (e.g., YAML):
use Symfony\Component\Dotenv\Parser\Parser;
class CustomParser extends Parser {
public function parse(string $content): array {
// Custom logic...
}
}
Event Listeners:
Hook into Dotenv events (e.g., after loading) via dependency injection:
$dotenv->addListener('afterLoad', function () {
// Post-processing logic
});
Laravel-Specific:
config/dotenv.php:
'path' => base_path('.env.custom'),
LoadEnvironmentVariables:
namespace App\Foundation\Bootstrap;
use Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables as Original;
class LoadEnvironmentVariables extends Original {
public function bootstrap(): void {
parent::bootstrap();
// Additional logic
}
}
Performance:
static $cachedEnv = null;
if (!$cachedEnv) {
$dotenv = new Dotenv();
$cachedEnv = $dotenv->load(__DIR__.'/../.env');
}
APP_ENV Priority:
.env.production.local are loaded after .env.production, so later files override earlier ones.Empty Values:
KEY= sets the value to an empty string (not null). Use KEY="" for clarity.Windows Paths:
/) or double backslashes (\\) in paths:
LOG_PATH=C:\\logs\\app
PHP Constants:
true, false, null) to prevent conflicts.Heredoc Syntax:
<<< 'EOF' for multi-line values (avoid <<<EOF to prevent syntax errors):
MESSAGE<<< 'EOF'
This is a
multi-line value.
EOF
How can I help you explore Laravel packages today?