phpexperts/laravel-env-polyfill
A lightweight polyfill that brings Laravel's env() helper and .env file loading to any PHP project. Read environment variables with Laravel-style syntax and sensible type casting, no framework required.
composer require phpexperts/laravel-env-polyfillcomposer.json first. The interesting bits are autoload.files (that's where the global env() helper gets registered, which means it exists as soon as vendor/autoload.php runs) and require (it will pull in vlucas/phpdotenv — note which major version, because the v2 Dotenv::load() API and the v3+ Dotenv::createImmutable()->load() API are not interchangeable).src/. There are usually only two things in a package like this: a copy of Laravel's pre-5.8 env() helper and a thin loader that points Dotenv at your .env file. Find out whether the loader is invoked automatically or whether you must call it (e.g. from a bootstrap file) — that single detail determines your entry-point boilerplate..env at the project root (copy from .env.example) and add both to .gitignore.<?php
require __DIR__ . '/vendor/autoload.php';
// plus whatever bootstrap call the package requires to load .env
$dsn = sprintf(
'mysql:host=%s;port=%s;dbname=%s',
env('DB_HOST', '127.0.0.1'),
env('DB_PORT', '3306'),
env('DB_DATABASE')
);
That's the whole value proposition: env('KEY', $default) in a codebase that has no Illuminate\Foundation\Application.
Bootstrap once, read everywhere else. Call the loader in exactly one place (front controller, CLI entry, PHPUnit bootstrap.php, cron wrapper) and nowhere else. If the package doesn't auto-load, write a tiny bootstrap.php that requires the autoloader and then loads .env; have every entry point require that file.
Convert to a config array early. Don't sprinkle env() calls through business logic — it's a getenv()/$_ENV lookup every time, and it makes defaults impossible to reason about. Do this instead:
// config.php
return [
'db' => [
'host' => env('DB_HOST', '127.0.0.1'),
'port' => (int) env('DB_PORT', 3306),
'database' => env('DB_DATABASE'),
'password' => env('DB_PASSWORD', ''),
],
'debug' => (bool) env('APP_DEBUG', false),
];
Then $config = require 'config.php'; once at boot and pass it down. This mirrors Laravel's own "never call env() outside config files" guidance and is the single highest-leverage habit with this package.
Cast explicitly. The helper returns strings (with a few magic conversions — see Gotchas). (int), (bool), filter_var(..., FILTER_VALIDATE_BOOLEAN) for anything non-string.
Per-environment files. A common pre-5.8 pattern is loading .env.{APP_ENV} on top of .env, or picking the file based on a --env CLI argument / APP_ENV shell variable. Do that in your bootstrap, not in application code.
Tests. Load .env.testing in phpunit.xml's bootstrap (or set the variables via <php><env name="..." value="..."/></php>), and rely on the immutable behavior of Dotenv so real environment variables win over the file. Never let a test read the developer's local .env — it's a flaky-test factory.
Migration shim. If the project is heading toward Laravel, keep all env() calls confined to config/*.php-style files so you can move the files into a real Laravel app and delete the polyfill with zero application-code changes.
NOASSERTION. Before shipping this in anything commercial, read the repo's LICENSE/composer.json license field and confirm terms. With 5 stars, 0 dependents and one maintainer, you're the QA department.env(). If the project ever pulls in laravel/framework or another polyfill that also defines it, you get a fatal "cannot redeclare function". Defensive move: fork and wrap the definition in if (!function_exists('env')), and remove the package the moment you adopt Laravel itself.vlucas/phpdotenv. If the package's loader fights you or its phpdotenv constraint pins you to an old version, delete it and call Dotenv::createImmutable(__DIR__)->safeLoad() directly — you lose maybe ten lines of code and gain control over the dependency.true/(true)/false/(false)/null/(null)/empty/(empty) from the raw string to native values. That means env('X', 'true') returns the boolean true, not 'true'. To get a literal string "true", quote it in .env (X="true"). This bites people reading feature-flag values.null default is ambiguous. env('MISSING') and env('MISSING', null) are indistinguishable from deliberately-unset configuration. Use a sentinel default or a required helper (env('DB_PASSWORD') ?? throw new RuntimeException(...)) for values that must exist..env. Dotenv (in immutable mode) does not overwrite variables already set in $_ENV/$_SERVER/getenv(). That's usually what you want in containers, but it means a stale export DB_HOST=... in your shell silently shadows .env. If debugging "my edit did nothing", check getenv('DB_HOST') first..env paths are typically resolved against the current working directory, not the script's directory. Always pass an absolute path (__DIR__) in your bootstrap, especially under cron, PHP-FPM, or when the script is invoked from another directory..env. Values with spaces, #, or = need quotes; unquoted # starts a comment mid-line. Multi-line values need double quotes. Validate on first run with a var_dump($_ENV) in dev.env() with php artisan config:cache doesn't apply, but the mirror-image problem does: every env() call is a lookup, and your .env is parsed on every request. On a hot path (or a high-traffic PHP-FPM app) that's real overhead — another reason to convert to an array at boot, or generate a cached config.php in your deploy step.env() returns null: (1) did vendor/autoload.php run before the call? (2) did the loader actually execute? (3) is the .env path absolute and readable? (4) is the key spelled exactly as in the file? (5) does var_dump($_ENV, $_SERVER) show it? Five questions that cover 95% of issues..env → OS environment → config/local.php (gitignored) — by merging arrays in your bootstrap. That gives you the escape hatch the polyfill itself doesn't offer.How can I help you explore Laravel packages today?