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

Env Laravel Package

php-standard-library/env

A lightweight PHP package from the Standard Library for inspecting and modifying environment variables, working directories, and filesystem paths, with simple, consistent functions that are easy to drop into any project.

View on GitHub
Deep Wiki
Context7

Getting Started

1. Confirm the package identity before you install anything. This repo is a split package from the php-standard-library/php-standard-library monorepo, and the README is a four-line stub — no API reference, no installation line. Before composer require, open composer.json in the repo and read the actual name, require.php constraint, and autoload block. Two things matter here:

  • The Composer name may be php-standard-library/env or a legacy azjezz/psl-era name depending on the split; grabbing the wrong one silently pulls a different package.
  • PSL 6.x targets modern PHP. Check the floor against your oldest service before you plan any rollout — a single PHP 8.1 app in the fleet can block adoption of the whole component.

2. Read the source, not the docs. With zero dependents and release notes that consist entirely of "See the main repository release for details," the only trustworthy source of truth is:

src/        # the function list — this is the entire public API surface
tests/      # effectively the documentation; each test is a usage example

The component is function-based (use function Psl\Env\...), not class-based, so there is no facade, no service provider, and no config/env.php to publish. Budget 20 minutes to read src/ end-to-end; it will be faster than searching for blog posts that don't exist.

3. Wire it into one CLI surface first. The highest-value, lowest-risk first use case in a Laravel codebase is console commands, queue workers, and deploy scripts — the places where Laravel's config() cache doesn't help and getenv() has always been the fallback:

use function Psl\Env\get_var;
use function Psl\Env\current_dir;
use function Psl\Env\set_current_dir;

// app/Console/Commands/BuildAssets.php
$root = get_var('BUILD_ROOT') ?? base_path();

set_current_dir($root);

try {
    // relative paths in this scope now resolve predictably,
    // regardless of where `php artisan` was invoked from
} finally {
    set_current_dir(base_path());
}

That try/finally is not optional. See the gotchas section.


Implementation Patterns

Wrap the functions behind an application-owned adapter

These are free functions with no interface behind them, which means no mocking seam. Rather than sprinkling use function Psl\Env\get_var; across 40 files, define one boundary:

// app/Support/Environment.php
namespace App\Support;

use function Psl\Env\get_var;

final class Environment
{
    public static function string(string $key, string $default = ''): string
    {
        $value = get_var($key);

        return $value === null || $value === '' ? $default : $value;
    }
}

Now tests can swap Environment::string() behind a bound interface, and you can add the strict boolean/integer parsing that the package itself does not provide (see gotchas). The PM/TPM notes floating around internally cite envBool(), envInt(), and envString() — verify those exist in src/ before designing around them; the published docs do not confirm them, and they read like aspirational API from a proposal doc rather than shipped code.

Split the reads by layer

The package is genuinely useful precisely where config() is not:

Layer Use Why
HTTP requests / controllers config() config:cache gives you a per-process snapshot; no env I/O per request
Queue workers, Horizon Environment::string() or get_var() Boot-time config cache is fine, but per-job external config (tenant buckets, feature endpoints) can shift
artisan commands, deploy scripts PSL directly No config cache exists; getenv() semantics are the only option
Shared libraries / non-Laravel packages PSL directly Framework-agnostic; no dependency on the Laravel container

Working directory discipline in deploy and build scripts

set_current_dir() / current_dir() shine in scripts that shell out to npm, rsync, or composer and are sensitive to where they were launched from. The pattern that scales:

$previous = current_dir();
set_current_dir($buildRoot);

try {
    // ... shell_exec, Process::run, etc.
} finally {
    set_current_dir($previous);
}

Always restore to the saved previous value, never to base_path(). Scripts get chained, and the next script in the chain expects the cwd it inherited.

Temp directories instead of hardcoded /tmp

For jobs that stage files (CSV exports, PDF renders, image resizing), use the package's temp-dir accessor rather than sys_get_temp_dir() or '/tmp'. It respects platform conventions and container mounts, which matters the first time you run the same job in a read-only-root Kubernetes pod.

Test isolation helper

Every function in this package touches process-global state. Write the helper once:

// tests/Concerns/IsolatesEnvironment.php
protected function withEnv(array $vars, Closure $callback): mixed
{
    $originals = array_map(getenv(...), array_keys($vars)) ?: [];

    foreach ($vars as $key => $value) {
        putenv("{$key}={$value}");
    }

    try {
        return $callback();
    } finally {
        foreach ($originals as $key => $value) {
            putenv($value === false ? $key : "{$key}={$value}");
        }
    }
}

Same idea for cwd. Without this, one test that mutates a variable or changes directory will produce order-dependent failures that look like framework bugs.

Use the filesystem functions for path work

If you're reaching for this package to "normalize, join, and resolve file paths" per the description, look in src/ for what's actually exported. Path manipulation in PSL traditionally lives alongside the filesystem utilities rather than in Env; if the split package only re-exports env/cwd helpers, you may still need Psl\Filesystem (or plain realpath() + DIRECTORY_SEPARATOR handling). Don't assume the README's feature bullet list maps 1:1 to this split's exports.


Gotchas and Tips

It is not a dotenv loader. Nothing here parses .env. You still need Laravel's bootstrap or vlucas/phpdotenv in non-Laravel contexts. The name invites the confusion.

Three environment stores, and this package probably touches one. getenv()/putenv(), $_ENV, and $_SERVER are distinct, and PHP's variables_order ini setting decides which get populated. Laravel's bootstrapper writes to several for compatibility. If a value set via this package isn't visible in a place that reads $_ENV directly — or vice versa — you're seeing the split, not a bug. Verify in one line before you file anything:

php -r 'var_dump(getenv("APP_ENV"), $_ENV["APP_ENV"] ?? null, $_SERVER["APP_ENV"] ?? null);'

The migration trap: Laravel's env() casts, this likely does not. Laravel's env() normalizes the literal strings "true", "false", "null", "empty", and strips surrounding quotes. A raw get_var() almost certainly returns the untouched string "false" — which is truthy in PHP. A mechanical replacement of env('X') with get_var('X') in a feature-flag check is a production incident waiting to happen. Audit every boolean/null comparison before you swap, and put the cast in your adapter, not in 40 call sites.

Mutating the process environment is global and leaks. In queue workers, Horizon, and especially Laravel Octane, the process survives many requests. An set_var() without a finally-unset poisons every subsequent request handled by that worker. Same for set_current_dir() — a stranded cwd in an Octane worker means every later Storage::put() with a relative path writes somewhere unexpected. Treat both as scoped transactions.

Don't reintroduce per-request env reads behind config:cache's back. The entire point of php artisan config:cache is to eliminate runtime env lookups in production. Calling these functions from a controller hot path quietly undoes that, and it's invisible in staging because staging usually isn't config-cached. If you need an env read in a request path, put it in config/*.php — where both env() and PSL functions execute once at cache-build time.

PHPStan/Psalm integration is good but function-shaped. PSL is aggressively typed, which is why the component plays well at high static-analysis levels. But use function imports are harder to trace than class references, and there's no interface to mock. If your test suite relies on seams, the adapter in the previous section isn't a nice-to-have — it's required.

Evaluate the parent repo, not this one. Zero stars, zero dependents, empty per-release notes, and a link-only README look alarming in isolation. They're an artifact of monorepo splitting: health lives in php-standard-library/php-standard-library (issues, CONTRIBUTING, the docs site, the real changelog). Make your adoption decision against the parent's activity and the 6.x release cadence — and read the monorepo changelog, because the per-package release notes carry no migration information at all.

Version pinning. The split ships in lockstep with the monorepo major (currently 6.3.0). Pin the major and follow the monorepo's upgrade guides, not this repo's.

No extension points, by design. There's no container binding, no plugin hook, no config file to override. That's a feature — there's nothing to conflict with Laravel's container. But it also means every behavioral need (strict parsing, required-vs-optional validation, defaults per environment) is yours to write. The PM doc's "roadmap for dynamic configs / secret manager loaders" is speculative; do not plan against it.

Not a caching layer, not a secrets manager, not a validator. No encryption, no schema, no memoization. If the request is "validated, cached, audit-logged configuration," this is one small piece and you'll be writing the rest.

MIT, zero runtime dependencies — safe to vendor. The risk calculus is entirely "is the API what I think it is," which is solvable in an afternoon by reading src/ and tests/. Do that before you pitch it upward; the docs won't do it for you.

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.
ably/pubsub-server
mstfkhazaal/defender
mfonte/base62x
karnoweb/metapilot
tobya/bcsapiwrapper
swalker2/cpanel
vish4395/laravel-translation-manager
morningtrain/economic
teksite/systeminfo-laravel
atomcms/voting
run6/weather
siokas/laravelembeddirectives
adamlien/pkuuids
gilsonsouza/lara-doc-generator
manelgavalda/enrollment_mobile
unabomber/timezone
delatbabel/contacts
astritzeqiri/g-recaptcha
kevinongko/laravel-genode
developeruz/laravel-analytics