Variables de entorno en PHP: la configuración que cambia de un servidor a otro, y sobre todo los secretos, no se escribe en el código. Se lee del entorno al arrancar, con tipos y comprobaciones, y el código es el mismo en tu ordenador y en producción.
También te puede interesar
Leer una variable: getenv
<?php
declare(strict_types=1);
var_dump(getenv('APP_ENTORNO'));
var_dump(getenv('NO_EXISTE'));
var_dump($_ENV['APP_ENTORNO'] ?? 'no está en $_ENV');
var_dump($_SERVER['APP_ENTORNO'] ?? 'no está en $_SERVER');
echo ini_get('variables_order'), "\n";APP_ENTORNO=desarrollo php basico.php (sin php.ini)string(10) "desarrollo" bool(false) string(10) "desarrollo" string(10) "desarrollo" EGPCS
APP_ENTORNO=desarrollo php -d variables_order=GPCS basico.php (como en php.ini-production)string(10) "desarrollo" bool(false) string(17) "no está en $_ENV" string(10) "desarrollo" GPCS
getenv devuelve el valor como texto, o false si la variable no existe, y funciona igual con cualquier configuración. $_ENV, en cambio, depende de variables_order: sin ningún php.ini vale EGPCS y $_ENV trae las variables, pero las dos plantillas que trae PHP, php.ini-development y php.ini-production, lo cambian a GPCS, sin la E, y entonces $_ENV queda vacío. Por eso, en tu ordenador puede funcionar y en el servidor no. Usa getenv.
Una clase de configuración
Leer variables sueltas por todo el código acaba mal. Lo habitual es leerlas una vez, al arrancar, en un objeto con tipos, valores por omisión y errores claros:
<?php
declare(strict_types=1);
final readonly class Config
{
public function __construct(
public string $entorno,
public bool $depuracion,
public int $puerto,
#[\SensitiveParameter] public string $claveApi,
) {}
public static function desdeEntorno(): self
{
return new self(
entorno: self::texto('APP_ENTORNO', 'produccion'),
depuracion: filter_var(self::texto('APP_DEPURACION', 'false'), FILTER_VALIDATE_BOOL),
puerto: filter_var(self::texto('APP_PUERTO', '8000'), FILTER_VALIDATE_INT) ?: throw new RuntimeException('APP_PUERTO no es un número'),
claveApi: self::obligatoria('API_CLAVE'),
);
}
private static function texto(string $nombre, string $omision): string
{
$v = getenv($nombre);
return $v === false || $v === '' ? $omision : $v;
}
private static function obligatoria(string $nombre): string
{
$v = getenv($nombre);
if ($v === false || $v === '') {
throw new RuntimeException("Falta la variable de entorno $nombre");
}
return $v;
}
}
try {
$config = Config::desdeEntorno();
} catch (RuntimeException $e) {
fwrite(STDERR, $e->getMessage() . "\n");
exit(1);
}
echo "Entorno: {$config->entorno}\n";
echo "Depuración: ", var_export($config->depuracion, true), "\n";
echo "Puerto: {$config->puerto}\n";
echo "Clave de la API: ", str_repeat('*', 8), substr($config->claveApi, -4), "\n";Con todas las variables definidas:
APP_ENTORNO=desarrollo APP_DEPURACION=yes APP_PUERTO=8080 API_CLAVE=… php config.phpEntorno: desarrollo Depuración: true Puerto: 8080 Clave de la API: ********abcd
Solo con la obligatoria, las demás toman su valor por omisión:
API_CLAVE=… php config.phpEntorno: produccion Depuración: false Puerto: 8000 Clave de la API: ********wxyz
Y sin ella, el programa no arranca, con un mensaje claro y un código de salida distinto de 0 (aquí, 1):
php config.phpFalta la variable de entorno API_CLAVE
Detalles: todo llega como texto, así que FILTER_VALIDATE_BOOL convierte «yes», «true», «on» o «1» en true, y FILTER_VALIDATE_INT comprueba el número. La clave se muestra tapada, nunca entera, y #[\SensitiveParameter] la mantiene fuera de las trazas de error. Que falte una variable obligatoria es mejor detectarlo al arrancar que a mitad de una petición.
Los secretos no van en el código
Contraseñas de bases de datos, claves de API y tokens: nunca escritos en el código ni en el repositorio. Si se suben a Git, quedan en el historial aunque luego se borren, y cualquiera con acceso al repositorio (o a una copia filtrada) los tiene. En las variables de entorno, cada servidor tiene los suyos y el código es el mismo en todas partes.
Cómo se definen depende de dónde se ejecute PHP:
| Dónde | Cómo |
|---|---|
| La terminal | API_CLAVE=… php programa.php o export API_CLAVE=… |
| PHP-FPM | env[API_CLAVE] = … en el archivo del pool (con clear_env = no si vienen del sistema) |
| Apache | SetEnv API_CLAVE … en la configuración del sitio, fuera de la carpeta pública |
| Docker | docker run -e API_CLAVE=… o environment: en Compose, mejor con secrets |
| Desarrollo local | un archivo .env leído con la biblioteca vlucas/phpdotenv |
.env, añádelo a .gitignore desde el primer día y deja en el repositorio solo un .env.example con los nombres y valores falsos. Y que no esté dentro de la carpeta pública: un .env descargable es una de las filtraciones de credenciales más frecuentes de la web.Resumen
| Para | Usa |
|---|---|
| Leer una variable | getenv('NOMBRE'), que devuelve false si no existe |
| Un sí o no | filter_var($v, FILTER_VALIDATE_BOOL) |
| Un número | filter_var($v, FILTER_VALIDATE_INT) |
| Toda la configuración | una clase readonly que se crea al arrancar |
| Secretos | solo en variables de entorno; .env fuera de Git y de la web |
Todo el código de esta página se ejecutó con PHP 8.5.12 en un contenedor limpio, sin acceso a la red, antes de publicarla; las salidas y los mensajes de error están copiados de esa ejecución.