Cannot set an env var to false, as it gets interpreted as missing
I have an env var set via a .env file, which is populated into the environment by Symfony/dotenv. It is defined like so:
ENABLE_CACHE=false
This creates a problem, because EnvironmentVariableResolver::getEnvVariable() will then dutifully return false as the value of the variable. However, the resolve() method of the same class then does this:
if (false !== $value) {
return $value;
}
Presumably because getenv() wrongly uses false as its not-found value. But the net result is that it's impossible to set an env var to false, because it gets confused with "missing" (ie, not found in $_ENV, $_SERVER, or getenv(), so getenv()'s false return value is used). This is not good.
The correct solution, IMO, is to use a sentinel single-value-enum to indicate not-found, instead of an otherwise legitimate value. Baring that, returning null from getEnvVariable() to indicate not-found would likely also work, or at least conflict with fewer legitimate uses.
Addendum: I just verified, and I have additional code that is folding $_ENV['ENABLE_CACHE'] to a boolean rather than a string, early in the process, after DotEnv has loaded it. That's likely why I'm the first to run into this issue. However, that seems necessary to me given that on/off toggles are a very common use of env vars, and those really should be booleans in the code base for hopefully self-evident reasons. This issue should still be corrected.
Environment variables cannot be booleans in the strict sense of the term, as you said the root issue might be here:
I have additional code that is folding $_ENV['ENABLE_CACHE'] to a boolean rather than a string
I'm not sure we should account for code that changes the behavior of an environment variable to something non-standard. AFAIK the usual practice is ENABLE_CACHE=0 right?
That would still need to be cast to something somewhere, else you're still passing a string "0" to the service that's expecting it, which generally won't do what you intend. Since the $_ENV variable can be modified trivially by anyone, I don't think it's safe to just assume it's always going to be only-strings. Even null is a potentially conflicting sentinel value, I suppose, but less so than anything else. A dedicated enum is the only guaranteed conflict-free option.
That would still need to be cast to something somewhere
Yes there's an open discussion about that topic