kariricode/dotenv

The only PHP dotenv with auto type casting, AES-256-GCM encryption, OPcache caching, fluent validation DSL, environment-aware loading, and CLI tooling — zero dependencies, PHP 8.4+, ARFA 1.3.

Maintainers

Package info

github.com/KaririCode-Framework/kariricode-dotenv

Homepage

pkg:composer/kariricode/dotenv

Statistics

Installs: 16

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v2.0.0 2026-03-02 23:51 UTC

This package is auto-updated.

Last update: 2026-03-02 23:53:27 UTC


README

PHP 8.4+ License: MIT PHPStan Level 9 Tests Zero Dependencies ARFA KaririCode Framework

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

The Problem

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

The Solution

$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"

Requirements

Requirement Version
PHP 8.4 or higher
ext-openssl Optional (required for encryption)

Installation

composer require kariricode/dotenv

Quick Start

# 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"

Features

Auto Type Casting

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"]

Variable Interpolation

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 unset

Load Modes

use 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);

Environment Cascade (bootEnv)

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.local is always skipped when APP_ENV=test — ensuring reproducible test runs.

Multiple Files

$dotenv = new Dotenv(__DIR__, $config, '.env', '.env.local');
$dotenv->load();

Allow / Deny Lists (glob patterns)

// Only load DB_* variables
$config = new DotenvConfiguration(allowList: ['DB_*']);

// Load everything except SECRET*
$config = new DotenvConfiguration(denyList: ['SECRET*']);

Validation DSL

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

Schema-Based Validation (.env.schema)

[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');

Encryption

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"

OPcache Caching

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/O

Variable Processors

Transform 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());

Debug & Introspection

$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();

Architecture

Source layout

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

Key design decisions

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

Specifications

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

Project Stats

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)

Contributing

git clone https://github.com/KaririCode-Framework/kariricode-dotenv.git
cd kariricode-dotenv
composer install
kcode init
kcode quality  # Must pass before opening a PR

License

MIT License © Walmir Silva

Part of the KaririCode Framework ecosystem.

kariricode.org · GitHub · Packagist · Issues