The only PHP dotenv with AES-256-GCM encryption, OPcache caching, fluent validation DSL,
environment-aware cascade loading, and auto type casting — zero dependencies, PHP 8.4+.
Installation · Quick Start · Features · Validation DSL · Encryption · Architecture
Every PHP project reinvents the same wheel:
// No type safety — you get raw strings everywhere
$_ENV['DB_PORT'] // "5432" (string, not int)
$_ENV['DEBUG'] // "true" (string, not bool)
// No validation — missing vars discovered at runtime
// No encryption — secrets sit as plaintext in .env files
// No cascade — can't load .env.local over .env automatically$dotenv = new Dotenv(__DIR__);
$dotenv->load();
// Auto-typed
env('DB_PORT'); // 5432 (int)
env('DEBUG'); // true (bool)
// Validated before service boot
$dotenv->validate()
->required('DB_HOST', 'DB_PORT')
->isInteger('DB_PORT')->between(1, 65535)
->url('APP_URL')
->email('ADMIN_EMAIL')
->assert();
// Encrypted secrets — transparent decryption
// SECRET=encrypted:base64data...
$dotenv->get('SECRET'); // "my-actual-secret"| Requirement | Version |
|---|---|
| PHP | 8.4 or higher |
| ext-openssl | Optional (required for encryption) |
composer require kariricode/dotenv# 1. Create your .env
APP_ENV=production
APP_URL=https://myapp.com
DB_HOST=localhost
DB_PORT=5432
APP_DEBUG=false<?php
require_once __DIR__ . '/vendor/autoload.php';
use KaririCode\Dotenv\Dotenv;
use function KaririCode\Dotenv\env;
// Load .env from project root
$dotenv = new Dotenv(__DIR__);
$dotenv->load();
// Typed access via helper
$port = env('DB_PORT'); // int: 5432
$debug = env('APP_DEBUG'); // bool: false
$host = env('DB_HOST'); // string: "localhost"All values are automatically cast to their native PHP type:
STRING_VAR=Hello World
INT_VAR=42
FLOAT_VAR=3.14
BOOL_VAR=true
NULL_VAR=null
JSON_VAR={"key": "value", "nested": {"subkey": "subvalue"}}
ARRAY_VAR=["item1", "item2", "item3"]env('STRING_VAR'); // string: "Hello World"
env('INT_VAR'); // int: 42
env('FLOAT_VAR'); // float: 3.14
env('BOOL_VAR'); // bool: true
env('NULL_VAR'); // null
env('JSON_VAR'); // array: ["key" => "value", "nested" => [...]]
env('ARRAY_VAR'); // array: ["item1", "item2", "item3"]APP_NAME=KaririCode
GREETING="Welcome to ${APP_NAME}" # → "Welcome to KaririCode"
HAS_REDIS=${REDIS_HOST:+yes} # "yes" if REDIS_HOST is set
FALLBACK=${MISSING_VAR:-default-value} # "default-value" if unsetuse KaririCode\Dotenv\Enum\LoadMode;
use KaririCode\Dotenv\ValueObject\DotenvConfiguration;
// Immutable (default) — skip vars already in environment
$config = new DotenvConfiguration(loadMode: LoadMode::Immutable);
// SkipExisting — keep existing $_ENV values, skip .env values
$config = new DotenvConfiguration(loadMode: LoadMode::SkipExisting);
// Overwrite — .env always wins
$config = new DotenvConfiguration(loadMode: LoadMode::Overwrite);Loads files in priority order, later files overriding earlier ones:
.env → .env.local → .env.{APP_ENV} → .env.{APP_ENV}.local
$dotenv = new Dotenv(__DIR__, new DotenvConfiguration(loadMode: LoadMode::Overwrite));
$dotenv->bootEnv(); // reads APP_ENV from environment automatically
$dotenv->bootEnv('staging'); // explicit environment
.env.test.localis always skipped whenAPP_ENV=test— ensuring reproducible test runs.
$dotenv = new Dotenv(__DIR__, $config, '.env', '.env.local');
$dotenv->load();// Only load DB_* variables
$config = new DotenvConfiguration(allowList: ['DB_*']);
// Load everything except SECRET*
$config = new DotenvConfiguration(denyList: ['SECRET*']);Fluent, chainable validation with all errors collected before throwing (no fail-fast):
$dotenv->validate()
->required('DB_HOST', 'DB_PORT', 'APP_ENV')
->notEmpty('DB_HOST')
->isInteger('DB_PORT')->between(1, 65535)
->isBoolean('APP_DEBUG')
->allowedValues('APP_ENV', ['local', 'staging', 'production'])
->url('APP_URL')
->email('ADMIN_EMAIL')
->matchesRegex('BUILD_SHA', '/^[a-f0-9]{40}$/')
->ifPresent('REDIS_HOST')->notEmpty()
->custom('DB_DSN', fn(string $v): bool => str_starts_with($v, 'pgsql:'))
->assert(); // throws ValidationException with ALL failures at once[DB_HOST]
required = true
notEmpty = true
[DB_PORT]
required = true
type = integer
min = 1
max = 65535
[APP_ENV]
required = true
allowed = local, staging, production$dotenv->loadWithSchema('/path/to/.env.schema');AES-256-GCM authenticated encryption for secrets. Encrypted values use the encrypted: prefix and are transparently decrypted on load.
use KaririCode\Dotenv\Security\KeyPair;
use KaririCode\Dotenv\Security\Encryptor;
// Generate a key pair (store the private key securely!)
$keyPair = KeyPair::generate();
$encryptor = new Encryptor($keyPair->privateKey);
// Encrypt a secret
$encrypted = $encryptor->encrypt('my-secret-password');
// → "encrypted:base64encodedpayload..."# .env — commit this (value is opaque ciphertext)
DB_PASSWORD=encrypted:aGVsbG8gd29ybGQ...// Decryption happens transparently during load
$config = new DotenvConfiguration(encryptionKey: $keyPair->privateKey);
$dotenv = new Dotenv(__DIR__, $config);
$dotenv->load();
$dotenv->get('DB_PASSWORD'); // "my-secret-password"Compile parsed variables into an OPcache-friendly PHP file. Subsequent requests load from shared memory with zero parsing cost:
$dotenv->load();
$dotenv->dumpCache('/path/to/.env.cache.php');
// Next request:
$config = new DotenvConfiguration(cachePath: '/path/to/.env.cache.php');
$dotenv = new Dotenv(__DIR__, $config);
$dotenv->load(); // loaded from OPcache — no file I/OTransform values after parsing, with glob pattern matching for key selection:
use KaririCode\Dotenv\Processor\CsvToArrayProcessor;
use KaririCode\Dotenv\Processor\UrlNormalizerProcessor;
use KaririCode\Dotenv\Processor\TrimProcessor;
use KaririCode\Dotenv\Processor\Base64DecodeProcessor;
$dotenv->addProcessor('ALLOWED_IPS', new CsvToArrayProcessor()); // "a, b, c" → ["a","b","c"]
$dotenv->addProcessor('*_URL', new UrlNormalizerProcessor()); // glob: all *_URL keys
$dotenv->addProcessor('API_TOKEN', new Base64DecodeProcessor());
$dotenv->addProcessor('DB_HOST', new TrimProcessor());$dotenv->load();
// Source tracking — where did each variable come from?
$debug = $dotenv->debug();
// ['DB_HOST' => ['source' => '.env.local', 'type' => 'String', 'value' => 'localhost', 'overridden' => true]]
// All loaded variables as EnvironmentVariable value objects
$vars = $dotenv->variables();
// Safe load — skip missing files instead of throwing
$dotenv->safeLoad();src/
├── Cache/ OPcache-friendly PHP file cache (PhpFileCache)
├── Contract/ Interfaces: TypeCaster · TypeDetector · ValidationRule · VariableProcessor
├── Core/ DotenvParser — full .env syntax support
├── Dotenv.php Main facade — load · validate · encrypt · cache · bootEnv
├── Enum/ LoadMode · ValueType
├── Exception/ DotenvException hierarchy (5 classes)
├── Processor/ CsvToArray · UrlNormalizer · Trim · Base64Decode
├── Schema/ SchemaParser — .env.schema declarative validation
├── Security/ Encryptor (AES-256-GCM) · KeyPair
├── Type/ TypeSystem + 6 Detectors + 6 Casters
├── Validation/ EnvironmentValidator (fluent DSL) + 10 Rule classes
├── ValueObject/ DotenvConfiguration (immutable) · EnvironmentVariable (immutable)
└── env.php Global env() helper
| Decision | Rationale | ADR |
|---|---|---|
| Zero dependencies | No version conflicts, sub-ms boot | ADR-001 |
| Immutable configuration | Thread-safe, ARFA 1.3 compliant | ADR-002 |
| Pluggable type system | Extend without modifying framework code | ADR-003 |
| AES-256-GCM encryption | Authenticated encryption, nonce-per-value | ADR-004 |
| OPcache caching | Zero parse overhead in production | ADR-005 |
| Fluent validation DSL | Collect all errors before throwing | ADR-008 |
| Spec | Covers |
|---|---|
| SPEC-001 | .env file syntax and parsing rules |
| SPEC-002 | Type detection and casting |
| SPEC-003 | Encryption format and key derivation |
| SPEC-004 | Validation DSL API |
| SPEC-005 | .env.schema format |
| SPEC-006 | Variable processor contract |
| SPEC-007 | CLI tooling |
| Metric | Value |
|---|---|
| PHP source files | 38 |
| External runtime dependencies | 0 |
| Test suite | 205 tests · 396 assertions |
| PHPStan level | 9 |
| PHP version | 8.4+ |
| ARFA compliance | 1.3 |
| Encryption | AES-256-GCM |
| Type detection | 7 built-in types (extensible) |
| Validation rules | 10 built-in rules (extensible) |
| Variable processors | 4 built-in (extensible) |
git clone https://github.com/KaririCode-Framework/kariricode-dotenv.git
cd kariricode-dotenv
composer install
kcode init
kcode quality # Must pass before opening a PRPart of the KaririCode Framework ecosystem.
kariricode.org · GitHub · Packagist · Issues