🚨 ¡Nueva review! 🔇 Los mejores cascos con ANC del mercado: los Sony WH-1000XM4 . ¡Échale un ojo! 👀

Optimiza PHP: SplStack y SplQueue (Más allá del Array)

Deja de usar arrays para todo y empieza a usar las estructuras de datos nativas y potentes de PHP.

Escrito por domin el 23 de enero de 2026 · Actualizado el 8 de febrero de 2026

🏗️ Deja de abusar de los Arrays en PHP

En el mundo de PHP tenemos la costumbre de usar Arrays para absolutamente todo. ¿Una lista? pues array nene, ¿unos resultaditos de una query? pos otro array, ¿unas patatas fritas? ¡OTRO ARRAY!

Pero PHP tiene una librería estándar llamada SPL (Standard PHP Library) que ofrece estructuras de datos específicas, implementadas en C, que son más eficientes y semánticamente correctas. Hoy vemos por encima algunas de ellas.

PHP SPL Stack and Queue Data Structures

Madre mía que imagenes más guapas me hace el lleminai nene.

¿Qué es SPL y por qué debería importarte?

SPL viene incluida en PHP desde la versión 5. No tienes que instalar nada, ni composer, ni extensiones. Está ahí, esperando a que la uses. Y no la usa casi nadie, que es lo triste.

La librería incluye estructuras de datos, iteradores, interfaces y excepciones. Hoy nos centramos en las estructuras de datos, que es donde más jugo le puedes sacar en el día a día.

SplStack

Pila LIFO (Last In, First Out). El último que entra es el primero que sale.

SplQueue

Cola FIFO (First In, First Out). El primero que llega es el primero que se atiende.

SplFixedArray

Array de tamaño fijo e índices numéricos. Mucho menos consumo de memoria.

SplPriorityQueue

Cola con prioridades. Sale primero el de mayor prioridad, no el que llegó antes.

SplMinHeap / SplMaxHeap

Montículos que siempre mantienen el mínimo o máximo arriba. Ideal para rankings.

SplObjectStorage

Mapa que usa objetos como claves. Perfecto para asociar metadatos a entidades.


🥞 SplStack: El concepto LIFO

SplStack implementa el principio LIFO (Last In, First Out). El último en entrar es el primero en salir. Para entenderlo, imagínate una pila de platos, pones uno encima de otro, y para quitar uno tienes que quitar el de arriba primero.

¿Cómo se usa?

$pila = new SplStack();

// Añadir (Push)
$pila->push("Plato 1");
$pila->push("Plato 2");
$pila->push("Plato 3");

// El último añadido es el 'Top'
echo $pila->top(); // Salida: Plato 3

// Quitar (Pop)
echo $pila->pop(); // Salida: Plato 3
// Ahora el top es Plato 2

Métodos principales que necesitas conocer:

MétodoQué haceComplejidad
push($valor)Añade un elemento al tope de la pilaO(1)
pop()Quita y devuelve el elemento del topeO(1)
top()Mira el tope sin quitarlo (peek)O(1)
isEmpty()¿Está vacía?O(1)
count()Cuántos elementos tieneO(1)

Fíjate que todo es O(1). Con un array normal, si usas array_pop() también es O(1), pero si usas array_shift() para simular una pila al revés, es O(n) porque PHP tiene que reindexar todo el array. Con SplStack no hay ese problema.

✅ Caso Real 1: Sistema “Deshacer” (Undo)

Un clásicazo. Hoy en día, cuando vas a meter la zarpa en un proyecto y la lías, cada liada que metes se apila. Cuando pulsas Ctrl+Z, deshaces la última liada.

$historial = new SplStack();

// Usuario escribe
$historial->push("Te emborrachaste");
$historial->push("Insultaste al camarero");
$historial->push("Le montaste un pollo a tu pareja");

// Usuario pulsa Deshacer (Ctrl+Z)
$accion = $historial->pop();
echo "Deshaciendo: " . $accion; // ahora solo el camarero te odia

✅ Caso Real 2: Validar HTML o Paréntesis

¿Cómo sabe tu IDE que te falta cerrar un </div>? Usando una pila. Cada vez que encuentras una etiqueta de apertura <div>, la metes a la pila. Cuando encuentras una de cierre </div>, sacas de la pila. Si al final la pila no está vacía, falta cerrar algo. O si intentas sacar y está vacía, has cerrado de más.

function validarParentesis(string $codigo): bool {
    $pila = new SplStack();

    foreach (str_split($codigo) as $char) {
        if ($char === '(') {
            $pila->push($char);
        } elseif ($char === ')') {
            if ($pila->isEmpty()) return false; // Cerrado sin abrir
            $pila->pop();
        }
    }

    return $pila->isEmpty();
}

var_dump(validarParentesis("((Hola))")); // true
var_dump(validarParentesis("((Hola)"));  // false

✅ Caso Real 3: Navegación con historial (Back/Forward)

Otro caso que usamos todos los días. El botón de “Atrás” del navegador. Cada vez que visitas una página, se apila. Cuando pulsas atrás, se desapila y se mete en otra pila de “adelante”:

$historial = new SplStack();
$adelante = new SplStack();

// Navegamos
$historial->push('/inicio');
$historial->push('/productos');
$historial->push('/productos/teclado');

// Pulsamos "Atrás"
$paginaActual = $historial->pop(); // /productos/teclado
$adelante->push($paginaActual);
echo "Ahora en: " . $historial->top(); // /productos

// Pulsamos "Adelante"
$pagina = $adelante->pop();
$historial->push($pagina);
echo "Ahora en: " . $historial->top(); // /productos/teclado

1. ¿Qué significa LIFO y qué estructura SPL lo implementa?


🚶‍♂️ SplQueue: El concepto FIFO

SplQueue implementa FIFO (First In, First Out). El primero en llegar es el primero en ser atendido. Como la cola del supermercado o del cine.

¿Cómo se usa?

$cola = new SplQueue();

// Encolar (Enqueue)
$cola->enqueue("Cliente 1");
$cola->enqueue("Cliente 2");

// Ver quién va primero (Bottom porque es donde empieza la cola)
echo $cola->bottom(); // Cliente 1

// Desencolar (Dequeue)
echo $cola->dequeue(); // Cliente 1

Ojo a la confusión: En SplQueue, enqueue añade al final y dequeue saca del principio. bottom() mira el principio (el siguiente en salir) y top() mira el final (el último que llegó).

¿Por qué no array_shift() + array_push()?

Mucha gente simula una cola con arrays:

// ❌ Esto funciona pero es LENTO con arrays grandes
$cola = [];
$cola[] = 'tarea1';          // Añadir al final: O(1)
$primera = array_shift($cola); // Sacar del principio: O(n) 💀

array_shift() tiene que mover todos los elementos una posición hacia atrás y reindexar el array entero. Si tienes 100.000 elementos, mueve 100.000 elementos cada vez que sacas uno. Con SplQueue, dequeue() es O(1) siempre. La diferencia se nota, y mucho.

2. ¿Por qué array_shift() es un problema de rendimiento en colas grandes?

✅ Caso Real 1: Procesamiento de Tareas (Jobs)

Tienes un sistema que envía emails. No quieres bloquear al usuario mientras se envía. Guardas la tarea en una cola y un “worker” la procesa en orden.

$colaEmails = new SplQueue();

// Añadimos tareas
$colaEmails->enqueue("email_bienvenida@usuario.com");
$colaEmails->enqueue("reporte_mensual@admin.com");

// Worker procesando
while (!$colaEmails->isEmpty()) {
    $email = $colaEmails->dequeue();
    enviarEmail($email);
    echo "Enviado a: $email\n";
}

✅ Caso Real 2: Chatbot con Respuestas en Espera

Imagina un chatbot de soporte, los mensajes de los usuarios entran en orden. Debes procesarlos uno a uno, respetando quién llegó primero. Si usaras una pila, contestarías al último que escribió primero y eso no es muy justo.

$chatServer = new SplQueue();

$chatServer->enqueue(['user' => 'Juan', 'msg' => 'Tengo un problema']);
$chatServer->enqueue(['user' => 'Ana', 'msg' => 'Quiero un reembolso']);

// Procesando tickets
$ticketActual = $chatServer->dequeue(); // Atendemos a Juan primero

✅ Caso Real 3: Rate Limiter sencillo

Un caso que se ve mucho en APIs: limitar a X peticiones por minuto. Puedes usar una cola para llevar el control de las peticiones recientes:

$peticiones = new SplQueue();
$limite = 100; // máximo 100 peticiones
$ventana = 60; // en 60 segundos

function puedeHacerPeticion(SplQueue $peticiones, int $limite, int $ventana): bool {
    $ahora = time();

    // Limpiar peticiones antiguas (fuera de la ventana)
    while (!$peticiones->isEmpty() && $peticiones->bottom() < $ahora - $ventana) {
        $peticiones->dequeue();
    }

    if ($peticiones->count() >= $limite) {
        return false; // Límite alcanzado
    }

    $peticiones->enqueue($ahora);
    return true;
}

La cola aquí es perfecta porque las peticiones más antiguas siempre están al principio (FIFO), así que limpiarlas es muy rápido.


🏎️ Bonus: SplFixedArray (El Array de verdad)

Si te preguntas si existe algo en SPL que actue como un array normal pero que sea más eficiente, pos si, existe, es SplFixedArray.

Los arrays de PHP son en realidad mapas ordenados (hash maps). Son super flexibles (puedes mezclar tipos, claves no numéricas…), pero esa flexibilidad tiene un coste de memoria y velocidad.

SplFixedArray es lo más parecido a un array de C.

Características clave:

¿Cuándo usarlo?

Cuando sepas de antemano el tamaño de tu colección y solo necesites índices numéricos (matrices, procesamiento de imágenes, datasets grandes).

// Un array normal de PHP
$array = [];
$array[0] = 3;
$array[100] = 5; // PHP tiene que gestionar este hueco, es un hash map

// SplFixedArray
$fixed = new SplFixedArray(5); // Tamaño 5
$fixed[0] = 1;
$fixed[1] = 2;
// $fixed[10] = 5; // ¡ERROR! RuntimeException: Index invalid or out of range

// Redimensionar si es necesario
$fixed->setSize(10);

⛔ ¡Cuidado con los bucles! Si no sabes el tamaño máximo de antemano, NO uses SplFixedArray redimensionando en cada iteración ($fixed->setSize($i + 1)). Esto destruye el rendimiento porque PHP tiene que reservar nueva memoria y copiar datos todo el rato.

Si no sabes el tamaño, usa un array normal o una SplDoublyLinkedList y, si al final necesitas la velocidad de lectura, conviértelo:

$fixed = SplFixedArray::fromArray($miArrayNormal);

3. ¿Cuánta memoria ahorra SplFixedArray respecto a un array normal con 1 millón de enteros?

⚡ Comparativa rápida de memoria

Si llenas un array con 1 millón de enteros:

Una diferencia bastante considerable pue.

Convertir entre SplFixedArray y array

Ir y venir entre ambos es muy fácil, así que puedes usar SplFixedArray donde necesites rendimiento y convertir cuando necesites flexibilidad:

// De array normal a SplFixedArray
$datos = [10, 20, 30, 40, 50];
$fixed = SplFixedArray::fromArray($datos);

// De SplFixedArray a array normal
$arrayNormal = $fixed->toArray();

// Iterar funciona igual que un array
foreach ($fixed as $indice => $valor) {
    echo "$indice: $valor\n";
}

// count() también funciona
echo count($fixed); // 5

🌌 El Universo SPL es enorme

No solo existen esas SPL. PHP está repleto de estructuras ocultas que solucionan problemas complejos con mucha elegancia. Aquí tienes otras pocas más.

🏥 SplPriorityQueue (Cola de Urgencias)

En una cola normal (SplQueue), sale primero quien llega primero. Pero, ¿y si es un hospital? Si llega alguien con un ataque al corazón, debe pasar antes que el que tiene un resfriado, aunque haya llegado más tarde.

Para eso sirve SplPriorityQueue.

$urgencias = new SplPriorityQueue();

// Insertamos (Dato, Prioridad)
$urgencias->insert('Paciente Resfriado', 1); // Prioridad baja
$urgencias->insert('Paciente Infarto', 10);  // Prioridad ALTA
$urgencias->insert('Paciente Fiebre', 5);    // Prioridad media

// Al iterar, salen ordenados por PRIORIDAD, no por llegada
foreach ($urgencias as $paciente) {
    echo $paciente . PHP_EOL;
}

// Salida:
// Paciente Infarto
// Paciente Fiebre
// Paciente Resfriado

Un detalle que no se suele contar: por defecto SplPriorityQueue solo te devuelve los datos al iterar. Si necesitas ver también la prioridad:

$urgencias->setExtractFlags(SplPriorityQueue::EXTR_BOTH);

foreach ($urgencias as $item) {
    echo $item['data'] . " (prioridad: " . $item['priority'] . ")\n";
}

🌳 SplMinHeap (El más barato primero)

Imagina que tienes una lista de precios de vuelos y siempre quieres sacar el más barato rápidamente. SplMinHeap mantiene los datos ordenados en forma de árbol, donde el nodo superior siempre es el menor.

$precios = new SplMinHeap();
$precios->insert(150);
$precios->insert(50);
$precios->insert(300);

echo $precios->top(); // 50 (El más barato)

Ventaja: Si usaras un array, tendrías que hacer sort($array) cada vez que metes un precio nuevo (lento). SplMinHeap lo reordena internamente de forma súper eficiente al insertar.

Y si quieres el más caro primero, tienes SplMaxHeap que funciona exactamente igual pero al revés:

$precios = new SplMaxHeap();
$precios->insert(150);
$precios->insert(50);
$precios->insert(300);

echo $precios->top(); // 300 (El más caro)

🗺️ SplObjectStorage (Mapeando Objetos)

¿Alguna vez has querido asociar datos a un objeto pero sin “manchar” el objeto con propiedades nuevas? Los arrays normales de PHP NO permiten usar objetos como claves ($mapa[$usuario] = 'datos' da error).

SplObjectStorage es exactamente para esto: un mapa donde las claves son objetos.

$almacen = new SplObjectStorage();

$usuario1 = new User('Domin');
$usuario2 = new User('Pepe');

// Asociamos información extra a estos objetos
$almacen[$usuario1] = ['rol' => 'admin', 'ultimo_login' => 'ayer'];
$almacen[$usuario2] = ['rol' => 'invitado'];

// Podemos comprobar si un objeto está en el almacén
if ($almacen->contains($usuario1)) {
    echo "Datos de Domin: " . $almacen[$usuario1]['rol']; // admin
}

Es utilísimo para patrones como Data Mapper o para adjuntar metadatos temporales a entidades de Doctrine/ORM sin tocar la entidad.

🔗 SplDoublyLinkedList (Lista doblemente enlazada)

Esta es la base de la que heredan SplStack y SplQueue. Si necesitas insertar y borrar elementos en cualquier posición de forma eficiente (no solo al principio o al final), esta es tu estructura:

$lista = new SplDoublyLinkedList();

$lista->push('A');
$lista->push('B');
$lista->push('C');

// Insertar en una posición concreta
$lista->add(1, 'X'); // Inserta 'X' en la posición 1

// Ahora es: A, X, B, C
foreach ($lista as $valor) {
    echo $valor . " ";
}

Con un array, insertar en medio con array_splice() obliga a PHP a mover todos los elementos posteriores. Con SplDoublyLinkedList, la inserción es O(1) una vez tienes el iterador en la posición (aunque llegar ahí es O(n)).


📊 Comparativa: ¿Array vs SPL?

Esta es la tabla que necesitas guardar en favoritos. Compara las operaciones más comunes y su complejidad:

OperaciónArray PHPSPL equivalenteVeredicto
Añadir al final$arr[] = x → O(1)push() / enqueue() → O(1)Empate
Sacar del finalarray_pop() → O(1)pop() → O(1)Empate
Sacar del principioarray_shift()O(n)dequeue()O(1)SPL gana
Acceso por índice$arr[5] → O(1)SplFixedArray[5] → O(1)Empate (menos RAM en SPL)
Memoria (1M enteros)~40 MB~8 MB (SplFixedArray)SPL gana (5x)
Obtener mínimo/máximomin() / max() → O(n)SplMinHeap::top()O(1)SPL gana

🚀 ¿Por qué usar SPL en vez de Arrays?

  1. Rendimiento: Están optimizadas en C. Para grandes volúmenes de datos, SplStack, SplQueue y SplFixedArray gestionan la memoria mejor que un array gigante de PHP al que le haces array_shift (que tiene que reindexar todo el array cada vez, ¡costoso!).
  2. Semántica: Si ves new SplStack() en el código, sabes INSTANTÁNEAMENTE que es una pila. Si ves $arr = [], tienes que leer como se usa para saber qué es.
  3. Encapsulamiento: Tienes métodos claros (push, pop, enqueue, setSize) en lugar de funciones genéricas de array.

⚠️ Errores comunes y cosas que te pillan

Antes de que salgas corriendo a meter SPL en todos lados, hay trampas que conviene conocer:

1. Iterar sobre SplStack va al revés

Esto pilla a todo el mundo. Cuando iteras con foreach sobre una SplStack, los elementos salen en orden LIFO (del tope al fondo), no de abajo a arriba:

$pila = new SplStack();
$pila->push('A');
$pila->push('B');
$pila->push('C');

foreach ($pila as $valor) {
    echo $valor; // Imprime: C B A (no A B C)
}

Si quieres iterar de abajo a arriba (como un array), cambia el modo de iteración:

$pila->setIteratorMode(SplDoublyLinkedList::IT_MODE_FIFO);
foreach ($pila as $valor) {
    echo $valor; // Ahora sí: A B C
}

2. SplQueue se vacía al iterar

Por defecto, cuando iteras una SplQueue con foreach, los elementos se van eliminando. Después del foreach, la cola está vacía:

$cola = new SplQueue();
$cola->enqueue('tarea1');
$cola->enqueue('tarea2');

foreach ($cola as $tarea) {
    echo $tarea; // tarea1, tarea2
}

echo $cola->count(); // 0 (¡vacía!)

Si no quieres que se vacíe, cambia el modo:

$cola->setIteratorMode(SplDoublyLinkedList::IT_MODE_FIFO | SplDoublyLinkedList::IT_MODE_KEEP);

3. pop() y dequeue() en vacío lanzan excepción

A diferencia de array_pop() que devuelve null si el array está vacío, las funciones SPL lanzan un RuntimeException. Siempre comprueba con isEmpty() antes:

$pila = new SplStack();

// ❌ Esto peta
// $pila->pop(); // RuntimeException: Can't pop from an empty datastructure

// ✅ Esto está bien
if (!$pila->isEmpty()) {
    $pila->pop();
}

4. SplFixedArray no se serializa bien con json_encode

json_encode() no sabe qué hacer con un SplFixedArray (lo serializa como un objeto, no como un array). Si necesitas pasar datos a JSON:

$fixed = new SplFixedArray(3);
$fixed[0] = 'a';
$fixed[1] = 'b';
$fixed[2] = 'c';

// ❌ No funciona como esperas
echo json_encode($fixed); // {} o algo raro

// ✅ Convierte primero
echo json_encode($fixed->toArray()); // ["a","b","c"]

🗺️ Guía rápida: ¿Cuál uso?

Para que no te lies, esta es la chuleta definitiva:

Necesito deshacer/rehacer acciones

Usa SplStack. LIFO es justo lo que necesitas.

Necesito procesar tareas en orden

Usa SplQueue. FIFO respeta el orden de llegada.

Tengo muchos datos con índice numérico

Usa SplFixedArray. 5x menos memoria que un array.

Necesito procesar por prioridad

Usa SplPriorityQueue. Sale primero el más urgente.

Necesito el mínimo/máximo siempre accesible

Usa SplMinHeap o SplMaxHeap. top() en O(1).

Necesito asociar datos a objetos

Usa SplObjectStorage. Objetos como claves, sin ensuciar la entidad.

Necesito insertar/borrar en medio

Usa SplDoublyLinkedList. Inserción eficiente en cualquier posición.

Es un caso simple o necesito claves string

Usa un array normal. No todo tiene que ser SPL.


Conclusión

Los arrays molan y molarán pero PHP tiene herramientas profesionales que igual también van bien y son más eficientes. La próxima vez que necesites procesar datos en orden (Colas) o deshacer acciones (Pilas), acuérdate de SPL.

La clave no es reemplazar todos tus arrays por SPL. Es saber cuándo un array se queda corto y qué alternativa usar. Si estás haciendo array_shift() en un bucle con miles de elementos, eso es una cola gritándote que la uses. Si tienes un millón de enteros en memoria, ese SplFixedArray te va a ahorrar 32 MB de RAM.

¡EA! Nos beermos en los bares shavales!🍻


Pon a prueba lo aprendido

5. ¿Qué ocurre si llamas a pop() en una SplStack vacía?

6. En SplPriorityQueue, ¿cómo puedes obtener tanto los datos como la prioridad al iterar?

7. ¿Cuál es la principal limitación de SplFixedArray frente a un array normal?

8. ¿Cuál es la complejidad de obtener el mínimo en SplMinHeap con top()?