Hardcodear credenciales, claves de API o URLs de base de datos directamente en el código fuente es uno de los errores más comunes y más peligrosos en desarrollo web. Si el repositorio se hace público accidentalmente o un colaborador tiene acceso al código pero no debería tener acceso a producción, las credenciales quedan expuestas. La solución estándar es usar variables de entorno: valores que viven fuera del código y que cada entorno (local, staging, producción) configura de forma independiente.
getenv() y $_ENV
<?php
// getenv() lee del entorno del proceso
$dbHost = getenv('DB_HOST');
// $_ENV es el superglobal equivalente
$dbHost = $_ENV['DB_HOST'] ?? null;
// Con valor por defecto
$puerto = (int) getenv('APP_PORT') ?: 8080;
// Diferencia: getenv() puede leer del entorno del sistema aunque
// variables_order en php.ini no incluya 'E'; $_ENV solo si 'E' está incluida
Ficheros .env con vlucas/phpdotenv
La librería vlucas/phpdotenv carga un fichero .env al arrancar la aplicación y convierte cada línea en una variable de entorno. Es el estándar de facto en proyectos PHP modernos (Symfony, Laravel, Slim
).
# Instalar
# composer require vlucas/phpdotenv
# .env (nunca commitear este fichero)
APP_ENV=development
APP_DEBUG=true
DB_HOST=127.0.0.1
DB_PORT=3306
DB_NAME=mi_app
DB_USER=root
DB_PASS=secreto
MAIL_HOST=smtp.mailtrap.io
MAIL_PORT=2525
MAIL_USER=user123
MAIL_PASS=pass456
API_KEY_STRIPE=sk_test_abc123
<?php
require_once __DIR__ . '/vendor/autoload.php';
// Cargar el fichero .env del directorio raíz del proyecto
$dotenv = DotenvDotenv::createImmutable(__DIR__);
$dotenv->load();
// Ahora están disponibles tanto en $_ENV como en getenv()
echo $_ENV['APP_ENV']; // development
echo getenv('DB_HOST'); // 127.0.0.1
Validar variables obligatorias
Con phpdotenv puedes declarar qué variables son obligatorias y qué formato deben tener. Si falta alguna, la aplicación lanza una excepción en el arranque (mejor que un error críptico más adelante):
<?php
$dotenv = DotenvDotenv::createImmutable(__DIR__);
$dotenv->load();
$dotenv->required(['DB_HOST', 'DB_NAME', 'DB_USER', 'DB_PASS']);
$dotenv->required('APP_ENV')->allowedValues(['development', 'staging', 'production']);
$dotenv->required('DB_PORT')->isInteger();
$dotenv->required('APP_DEBUG')->isBoolean();
Ficheros por entorno: .env, .env.local, .env.production
Un patrón habitual es tener varios ficheros con distintas precedencias:
<?php
$dotenv = DotenvDotenv::createImmutable(__DIR__, ['.env', '.env.local']);
$dotenv->safeLoad(); // No lanza excepción si el fichero no existe
.env: valores por defecto para desarrollo, se commitea con valores no sensibles..env.local: sobreescrituras locales de cada desarrollador, nunca se commitea.- En producción, las variables se inyectan directamente en el proceso (via systemd, Docker, Kubernetes
) sin fichero
.env.
Inyectar variables en producción sin .env
# Docker / docker-compose
environment:
DB_HOST: db
DB_PASS: ${DB_PASS} # recogida del entorno del host
# systemd (en el unit file)
[Service]
Environment=DB_HOST=127.0.0.1
Environment=DB_PASS=secreto_produccion
# Kubernetes (como Secret)
env:
- name: DB_PASS
valueFrom:
secretKeyRef:
name: db-secret
key: password
Acceso seguro con null coalescing
<?php
// NUNCA asumir que la variable existe sin un valor por defecto o validación previa
$debug = filter_var($_ENV['APP_DEBUG'] ?? 'false', FILTER_VALIDATE_BOOLEAN);
// Helper recomendable para leer variables tipadas
function env(string $key, mixed $default = null): mixed {
$valor = $_ENV[$key] ?? getenv($key);
if ($valor === false || $valor === null) return $default;
return match(strtolower((string)$valor)) {
'true', '1', 'yes' => true,
'false', '0', 'no' => false,
'null', '' => null,
default => $valor,
};
}
$debug = env('APP_DEBUG', false); // bool
$puerto = (int) env('DB_PORT', 3306); // int
$host = env('DB_HOST', 'localhost'); // string
Qué ficheros añadir al .gitignore
# .gitignore
.env
.env.local
.env.*.local
!.env.example # Sí commitear el fichero de ejemplo sin valores reales
Siempre incluye un fichero .env.example en el repositorio con todas las variables necesarias pero sin sus valores reales. Cada desarrollador copia ese fichero a .env y pone sus valores locales.
