compact() y extract() en PHP: convertir entre arrays y variables del scope

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_SKIP o filtrando las claves antes.
  • Nunca pases $_POST, $_GET ni $_REQUEST directamente a extract().
  • En proyectos con arquitectura clara, suele ser preferible el array literal o los DTOs tipados.

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP