php-cooper

Dotenv
in package

FinalYes

Builds the environment `${...}` references (CASC.md §7.2) read -- the Elixir original's `Cooper.Dotenv`. `Cooper` calls it on every load, cached or not, so `${...}` values are always current.

Layering

Lowest precedence first, later winning:

  1. .env
  2. .env.<env> -- <env> is the dotenvEnv option when given (null meaning "no per-environment file"); otherwise COOPER_ENV (see below) as the real environment, env and the base .env set it -- so .env.dev, .env.staging, .env.test and .env.prod in every Cooper, matching env/${COOPER_ENV}.casc. Naming it by APP_ENV, as this once did, read Laravel's .env.production or .env.testing beside documents that read prod or test.
  3. .env.local
  4. the real process environment (getenv())
  5. the env option -- always the final override.

The real environment outranks the files. A deployment sets variables in the environment it controls; a file in the working directory must not silently beat them (Ruby's and Node's dotenv, and phpdotenv's own immutable mode, behave the same). Pass dotenvOverride: true to put the files on top instead.

dotenvFiles replaces the default file list (layers 1-3) outright. A missing file is silently skipped. Paths are relative to dotenvDir, which defaults to the project root -- deliberately not root (the CASC file's directory): .env files live at the project root wherever the CASC file sits and wherever the process was started. The project root is the Composer root package's directory, else the working directory (see projectRoot()). An absolute dotenvFiles entry is used as it is.

env is an override layer, not a replacement: a name it does not set still falls through to the files and the real environment.

COOPER_ENV

The one name a document reads the current environment by, in every Cooper implementation (CASC.md §7.2): import "env/${COOPER_ENV}.casc". Every layer above is consulted first, so a real COOPER_ENV -- in the environment, a .env file, or env -- always wins. Unset or empty, it falls back to this host's own name for the same thing: APP_ENV from those same layers, else "dev". Every Cooper names its environments dev, staging, test and prod, so in that fallback only, the host's own spellings are mapped onto them through the one table CASC.md §7.2 gives every implementation -- development and local -> dev, testing -> test, production -> prod, exact and case-sensitive -- and Laravel's local/testing/production read the same env/dev.casc as every other host. Any other APP_ENV (staging, qa, ...) is passed through as is, and a real COOPER_ENV is never mapped: whoever set it chose the name. APP_ENV itself is left alone. Read before .env.<env>, the same value names that file (see "Layering").

Because it is part of the map every load resolves against, the cache's environment check (Cache) sees the fallback too: a tree an import "${COOPER_ENV}.casc" shaped goes stale when APP_ENV changes.

File loading (layers 1-3) is on by default; dotenv: false turns it off. The files are parsed by vlucas/phpdotenv, which Cooper requires: as an optional package, the default silently read nothing in any application that had not added it itself.

Exporting to the process environment

Neither env() nor a load ever changes the process environment: the layering only builds the map ${...} resolves against. export() exists for one caller -- a framework integration that replaces the framework's own .env loader, after which code reading the process environment directly ($_ENV, $_SERVER, getenv(), Laravel's env(), Symfony's APP_ENV/APP_DEBUG) must still find what the files say. It is host-specific behaviour of this library, not CASC's.

Table of Contents

Constants

BASE_FILE  : mixed = '.env'
LOCAL_FILE  : mixed = '.env.local'
SHARED_ENV_NAMES  : mixed = ['development' => 'dev', 'local' => 'dev', 'tes...
Host environment names, and the shared ones (`dev`, `staging`, `test`, `prod`) they map onto -- the table CASC.md §7.2 gives every Cooper, so it must stay identical to theirs. Only the `APP_ENV` fallback consults it.

Methods

env()  : array<string, string>
The environment for `$options` (the same options array `Cooper` takes; only `env`, `dotenv`, `dotenvEnv`, `dotenvFiles`, `dotenvDir`, and `dotenvOverride` matter here).
export()  : array<string, string>
Writes what the `.env` files provided into the process environment -- `$_ENV`, `$_SERVER` and `putenv()` -- for code that reads it directly instead of through `${...}`. Never called by a load: only a caller that asks for it, typically a framework integration standing in for the framework's own `.env` loader, at bootstrap.
projectRoot()  : string
Where `.env` files are read from unless `dotenvDir` says otherwise: the Composer root package's directory -- the application, when Cooper is one of its dependencies -- else the working directory.
__construct()  : mixed
files()  : array<int, string>
The files to layer, lowest precedence first, as absolute paths.
inProcessEnvironment()  : bool
Whether the real process environment already holds `$name`: any of `getenv()`, `$_ENV`, or `$_SERVER` -- except a `$_SERVER` `HTTP_*` entry, which is a request header (see `export()`).
layered()  : array{0: array, 1: list}
Layers 1-5, before `COOPER_ENV` is filled in, and which names the files (layers 1-3) defined -- what `export()` writes.
nameEnv()  : string
`COOPER_ENV` as it stands before `.env.<env>` is read -- from the real environment, `env` and the base `.env` file, in the order the layering puts them -- which names the file to read next. The files after it cannot name it: they are not read yet.
onTop()  : array<string, string>
The real environment, the files and `env` stacked as `$options` says: `env` always last, the files above the real environment only under `dotenvOverride`.
parseFile()  : array<string, string>
present()  : bool

Constants

BASE_FILE

private mixed BASE_FILE = '.env'

LOCAL_FILE

private mixed LOCAL_FILE = '.env.local'

SHARED_ENV_NAMES

Host environment names, and the shared ones (`dev`, `staging`, `test`, `prod`) they map onto -- the table CASC.md §7.2 gives every Cooper, so it must stay identical to theirs. Only the `APP_ENV` fallback consults it.

private mixed SHARED_ENV_NAMES = ['development' => 'dev', 'local' => 'dev', 'testing' => 'test', 'production' => 'prod']

Methods

env()

The environment for `$options` (the same options array `Cooper` takes; only `env`, `dotenv`, `dotenvEnv`, `dotenvFiles`, `dotenvDir`, and `dotenvOverride` matter here).

public static env([array<string, mixed> $options = [] ]) : array<string, string>
Parameters
$options : array<string, mixed> = []
Tags
throws
CooperError

(stage dotenv) on a malformed .env file

InvalidArgumentException

on malformed options

Return values
array<string, string> —

name => value

export()

Writes what the `.env` files provided into the process environment -- `$_ENV`, `$_SERVER` and `putenv()` -- for code that reads it directly instead of through `${...}`. Never called by a load: only a caller that asks for it, typically a framework integration standing in for the framework's own `.env` loader, at bootstrap.

public static export([array<string, mixed> $options = [] ]) : array<string, string>

What is exported: every name the files define, and COOPER_ENV (always, so code outside CASC sees the environment name the configuration is loaded for, fallback included). Each gets the value ${NAME} resolves to under the same $options -- env included -- so the process environment and a load never disagree. A name only the env option sets is not exported: it is the caller's override for this load, not the files'.

The real environment is never overwritten, the precedence the layering uses: a name getenv(), $_ENV or $_SERVER already holds is left alone. The superglobals count because a web server can set a variable there without getenv() seeing it (Symfony's own loader checks the same places); a $_SERVER entry named HTTP_* does not, since it is a request header a client chose. dotenvOverride: true puts the files on top here too, and every name is written.

Idempotent: what one call wrote is the real environment to the next, which writes nothing (or, under dotenvOverride, the same values again). From then on, every load in the process sees those values as the real environment -- a .env file edited later is outranked by them, as by any other real variable.

Parameters
$options : array<string, mixed> = []

the options array Cooper takes (see env())

Tags
throws
CooperError

(stage dotenv) on a malformed .env file

InvalidArgumentException

on malformed options

Return values
array<string, string> —

name => value, every variable this call wrote

projectRoot()

Where `.env` files are read from unless `dotenvDir` says otherwise: the Composer root package's directory -- the application, when Cooper is one of its dependencies -- else the working directory.

public static projectRoot() : string

Reading them from the working directory only, as this once did, found none when an application was started from anywhere but its own root (a subdirectory, a worker, a cron job).

Return values
string —

an absolute directory

__construct()

private __construct() : mixed

files()

The files to layer, lowest precedence first, as absolute paths.

private static files(Options $options, array<string, string> $system) : array<int, string>
Parameters
$options : Options
$system : array<string, string>
Return values
array<int, string>

inProcessEnvironment()

Whether the real process environment already holds `$name`: any of `getenv()`, `$_ENV`, or `$_SERVER` -- except a `$_SERVER` `HTTP_*` entry, which is a request header (see `export()`).

private static inProcessEnvironment(string $name) : bool
Parameters
$name : string
Return values
bool

layered()

Layers 1-5, before `COOPER_ENV` is filled in, and which names the files (layers 1-3) defined -- what `export()` writes.

private static layered(Options $options) : array{0: array, 1: list}
Parameters
$options : Options
Return values
array{0: array, 1: list}

nameEnv()

`COOPER_ENV` as it stands before `.env.<env>` is read -- from the real environment, `env` and the base `.env` file, in the order the layering puts them -- which names the file to read next. The files after it cannot name it: they are not read yet.

private static nameEnv(Options $options, array<string, string> $system, string $dir) : string
Parameters
$options : Options
$system : array<string, string>
$dir : string

where .env is read from

Return values
string

onTop()

The real environment, the files and `env` stacked as `$options` says: `env` always last, the files above the real environment only under `dotenvOverride`.

private static onTop(Options $options, array<string, string> $system, array<string, string> $files) : array<string, string>
Parameters
$options : Options
$system : array<string, string>
$files : array<string, string>
Return values
array<string, string>

parseFile()

private static parseFile(string $file) : array<string, string>
Parameters
$file : string
Return values
array<string, string>

present()

private static present(string|null $value) : bool
Parameters
$value : string|null
Return values
bool
On this page

Search results