compact() y extract() son dos caras de la misma moneda: una convierte variables del scope actual en un array asociativo; la otra hace el camino inverso. Aparecen en muchos proyectos PHP pero también son fuente de errores y vulnerabilidades cuando se usan sin cuidado.
compact(): de variables a array
Recibe una lista de nombres de variables como strings y devuelve un array con esos nombres como claves y los valores de las variables como valores:
<?php
$nombre = 'Ana';
$edad = 28;
$ciudad = 'Barcelona';
$persona = compact('nombre', 'edad', 'ciudad');
// ['nombre' => 'Ana', 'edad' => 28, 'ciudad' => 'Barcelona']
// También acepta arrays de nombres
$campos = ['nombre', 'edad'];
$parcial = compact($campos);
// ['nombre' => 'Ana', 'edad' => 28]
// Si una variable no existe, la omite silenciosamente (PHP 8 lanza E_WARNING)
$mal = compact('nombre', 'inexistente');
// PHP 8: Warning: compact(): Undefined variable $inexistente
Usar compact() para pasar datos a plantillas
El uso más habitual es preparar el array de datos que se pasa a una vista o plantilla. Sin compact quedaría:
<?php
// Sin compact repetitivo
return view('perfil', [
'usuario' => $usuario,
'articulos' => $articulos,
'stats' => $stats,
]);
// Con compact más limpio
return view('perfil', compact('usuario', 'articulos', 'stats'));
Es un atajo de escritura, nada más. El resultado es idéntico.
extract(): de array a variables
Hace el camino inverso: crea variables en el scope actual a partir de las claves y valores de un array:
<?php
$datos = ['nombre' => 'Ana', 'edad' => 28, 'ciudad' => 'Barcelona'];
extract($datos);
echo $nombre; // Ana
echo $edad; // 28
// En una plantilla PHP clásica:
extract($view_data);
// Ahora puedes usar $titulo, $contenido, $autor directamente en el HTML
Los flags de extract()
El segundo parámetro controla qué hacer cuando ya existe una variable con ese nombre:
<?php
$nombre = 'Variable original';
$datos = ['nombre' => 'Del array', 'nuevo' => 'Valor'];
// EXTR_OVERWRITE (por defecto): sobreescribe la variable existente
extract($datos, EXTR_OVERWRITE);
echo $nombre; // Del array
// EXTR_SKIP: mantiene la variable original si ya existe
$nombre = 'Variable original';
extract($datos, EXTR_SKIP);
echo $nombre; // Variable original
// EXTR_PREFIX_ALL: añade prefijo a todas las variables
extract($datos, EXTR_PREFIX_ALL, 'arr');
echo $arr_nombre; // Del array
echo $arr_nuevo; // Valor
// EXTR_PREFIX_SAME: prefijo solo si hay conflicto
extract($datos, EXTR_PREFIX_SAME, 'arr');
echo $nombre; // Variable original (sin tocar)
echo $arr_nombre; // Del array (con prefijo porque había conflicto)
echo $nuevo; // Valor (sin prefijo porque no había conflicto)
El agujero de seguridad clásico con extract()
El error más peligroso: llamar a extract() directamente sobre $_POST, $_GET o $_REQUEST:
<?php
// MAL NUNCA hagas esto
extract($_POST);
// Si el atacante envía:
// POST: isAdmin=1&rol=superuser
// extract() crea $isAdmin = '1' y $rol = 'superuser'
// Puede sobreescribir cualquier variable de tu scope
// También peligroso: variables de sesión sin sanitizar
extract($_SESSION); // si $_SESSION puede ser manipulada, es igual de malo
Cuándo usar extract() de forma segura
La única situación razonablemente segura es cuando el array proviene de tu propio código y controlas exactamente qué claves tiene:
<?php
// BIEN: array definido por ti, sin entrada de usuario
function renderizar(string $plantilla, array $datos): void {
// Validar que los datos solo tienen las claves esperadas
$claves_permitidas = ['titulo', 'contenido', 'autor', 'fecha'];
$datos_seguros = array_intersect_key($datos, array_flip($claves_permitidas));
extract($datos_seguros, EXTR_SKIP);
include __DIR__ . "/plantillas/{$plantilla}.php";
}
// O mejor aún: pasar los datos directamente sin extract()
// En la plantilla: $datos['titulo'], $datos['contenido']...
// Más explícito y sin riesgo de colisiones
Alternativa recomendada
En la mayoría de los casos modernos, un array literal resulta más claro que compact/extract:
<?php
// compact() útil pero implícito
$nombre = 'Ana';
$edad = 28;
return compact('nombre', 'edad');
// Array literal explícito, más fácil de rastrear
return ['nombre' => $nombre, 'edad' => $edad];
// Con named arguments en PHP 8 es aún más limpio si tienes una clase
$persona = new Persona(nombre: 'Ana', edad: 28);
Ejemplo práctico: sistema de plantillas minimal
<?php
class Vista {
private string $dir;
public function __construct(string $dir) {
$this->dir = rtrim($dir, '/');
}
public function render(string $nombre, array $vars = []): string {
$archivo = "{$this->dir}/{$nombre}.php";
if (!file_exists($archivo)) {
throw new RuntimeException("Vista no encontrada: {$nombre}");
}
// Scope aislado extract no contamina el scope de Vista
$render = static function(string $__archivo, array $__vars): string {
extract($__vars, EXTR_SKIP);
ob_start();
include $__archivo;
return ob_get_clean();
};
return $render($archivo, $vars);
}
}
$vista = new Vista(__DIR__ . '/views');
echo $vista->render('perfil', compact('usuario', 'articulos'));
Resumen
- compact() es útil como atajo para ensamblar arrays a partir de variables del scope, sobre todo al llamar a funciones que esperan un array asociativo.
- extract() puede usarse en plantillas simples con datos controlados, siempre con
EXTR_SKIPo filtrando las claves antes. - Nunca pases
$_POST,$_GETni$_REQUESTdirectamente aextract(). - En proyectos con arquitectura clara, suele ser preferible el array literal o los DTOs tipados.
