SPL #
The Standard PHP Library (SPL) is a collection of data structures, iterators, and interfaces that has been part of PHP since version 5.0, yet is rarely used because many developers don’t know it exists or don’t know when to use it. SPL provides data structures that are far more efficient than arrays for certain use cases: SplStack for proper LIFO (can’t be randomly accessed like an array), SplQueue for efficient FIFO, SplHeap for priority queues that stay sorted, and SplFixedArray, which is far more memory-efficient than a regular array for fixed-size data collections. This article covers when each SPL data structure is the right choice and how to use them correctly.
When to Use SPL vs Regular Arrays #
flowchart TD
A{What data structure\ndo you need?} --> B{Access order?}
B -- "Random by index" --> C{Fixed size?}
C -- Yes --> D[SplFixedArray\n~40% more memory-efficient]
C -- No --> E[Regular array\nMost flexible]
B -- "LIFO — last in\nfirst out" --> F[SplStack\nClear semantics]
B -- "FIFO — first in\nfirst out" --> G[SplQueue\nEfficient enqueue/dequeue]
B -- "By priority" --> H[SplMinHeap / SplMaxHeap\nSplPriorityQueue]
B -- "Two directions — front & back" --> I[SplDoublyLinkedList]
style D fill:#dcfce7
style F fill:#dbeafe
style G fill:#fef9c3
style H fill:#f3e8ffSplStack — Last In First Out #
SplStack is a proper stack implementation — you can only push to the top and pop from the top. Unlike using an array as a stack, SplStack provides clear semantics and can’t be randomly accessed:
<?php
$stack = new SplStack();
// push — add to the top
$stack->push('first');
$stack->push('second');
$stack->push('third');
echo $stack->count(); // 3
// top — peek at the top without removing
echo $stack->top(); // "third"
// pop — take from the top (LIFO)
echo $stack->pop(); // "third"
echo $stack->pop(); // "second"
echo $stack->pop(); // "first"
// isEmpty — check whether it's empty
var_dump($stack->isEmpty()); // bool(true)
// Iteration — SplStack can be foreach'd (LIFO order)
$stack->push('a');
$stack->push('b');
$stack->push('c');
foreach ($stack as $value) {
echo $value . "\n"; // c, b, a (LIFO)
}
// Real example: undo/redo history
class EditorHistory
{
private SplStack $undoStack;
private SplStack $redoStack;
public function __construct()
{
$this->undoStack = new SplStack();
$this->redoStack = new SplStack();
}
public function do(string $action): void
{
$this->undoStack->push($action);
// After a new action, the redo history is cleared
while (!$this->redoStack->isEmpty()) {
$this->redoStack->pop();
}
echo "Do: $action\n";
}
public function undo(): ?string
{
if ($this->undoStack->isEmpty()) {
echo "Nothing to undo\n";
return null;
}
$action = $this->undoStack->pop();
$this->redoStack->push($action);
echo "Undo: $action\n";
return $action;
}
public function redo(): ?string
{
if ($this->redoStack->isEmpty()) {
echo "Nothing to redo\n";
return null;
}
$action = $this->redoStack->pop();
$this->undoStack->push($action);
echo "Redo: $action\n";
return $action;
}
}
$editor = new EditorHistory();
$editor->do("Type 'Hello'");
$editor->do("Bold text");
$editor->do("Change color to red");
$editor->undo(); // Undo: Change color to red
$editor->undo(); // Undo: Bold text
$editor->redo(); // Redo: Bold text
SplQueue — First In First Out #
SplQueue is an efficient queue implementation. Enqueue at the back, dequeue from the front:
<?php
$queue = new SplQueue();
// enqueue — add to the back of the queue
$queue->enqueue('task-1');
$queue->enqueue('task-2');
$queue->enqueue('task-3');
echo $queue->count(); // 3
// bottom — peek at the front without removing
echo $queue->bottom(); // "task-1"
// top — peek at the back
echo $queue->top(); // "task-3"
// dequeue — take from the front (FIFO)
echo $queue->dequeue(); // "task-1"
echo $queue->dequeue(); // "task-2"
// Iteration — SplQueue can be foreach'd (FIFO order)
$queue->enqueue('x');
$queue->enqueue('y');
foreach ($queue as $item) {
echo $item . "\n"; // task-3, x, y (FIFO)
}
// Iteration mode
$queue->setIteratorMode(SplDoublyLinkedList::IT_MODE_DELETE); // delete while iterating
foreach ($queue as $item) {
processItem($item); // every item is processed and removed from the queue
}
// The queue is now empty
// Real example: a simple job queue
class SimpleJobQueue
{
private SplQueue $queue;
public function __construct()
{
$this->queue = new SplQueue();
$this->queue->setIteratorMode(SplDoublyLinkedList::IT_MODE_FIFO);
}
public function addJob(array $job): void
{
$this->queue->enqueue($job);
echo "Job added: {$job['type']}\n";
}
public function processAllJobs(): void
{
while (!$this->queue->isEmpty()) {
$job = $this->queue->dequeue();
echo "Processing: {$job['type']}\n";
// process the job...
}
}
public function jobCount(): int
{
return $this->queue->count();
}
}
$jobQueue = new SimpleJobQueue();
$jobQueue->addJob(['type' => 'send_email', 'to' => '[email protected]']);
$jobQueue->addJob(['type' => 'resize_image', 'file' => 'photo.jpg']);
$jobQueue->addJob(['type' => 'generate_report', 'month' => 3]);
$jobQueue->processAllJobs();
SplMinHeap and SplMaxHeap — Basic Priority Queues #
A heap always keeps elements in order — SplMinHeap always puts the smallest value on top, SplMaxHeap the largest:
<?php
// SplMinHeap — the smallest value on top
$minHeap = new SplMinHeap();
$minHeap->insert(5);
$minHeap->insert(1);
$minHeap->insert(8);
$minHeap->insert(3);
echo $minHeap->top(); // 1 — the smallest is always on top
while (!$minHeap->isEmpty()) {
echo $minHeap->extract() . " "; // 1 3 5 8 — ascending order!
}
// SplMaxHeap — the largest value on top
$maxHeap = new SplMaxHeap();
$maxHeap->insert(5);
$maxHeap->insert(1);
$maxHeap->insert(8);
$maxHeap->insert(3);
while (!$maxHeap->isEmpty()) {
echo $maxHeap->extract() . " "; // 8 5 3 1 — descending order!
}
// Custom heap — override compare() for objects
class TaskHeap extends SplMinHeap
{
protected function compare(mixed $a, mixed $b): int
{
// compare() must return > 0 if $a should be above $b
// For MinHeap: smaller priority = more important = on top
// We want small priorities on top, so reverse the comparison
return $b['priority'] <=> $a['priority'];
}
}
$taskHeap = new TaskHeap();
$taskHeap->insert(['name' => 'Report', 'priority' => 3]);
$taskHeap->insert(['name' => 'Critical bug','priority' => 1]);
$taskHeap->insert(['name' => 'Meeting', 'priority' => 2]);
$taskHeap->insert(['name' => 'Email', 'priority' => 4]);
while (!$taskHeap->isEmpty()) {
$task = $taskHeap->extract();
echo "[{$task['priority']}] {$task['name']}\n";
}
// [1] Critical bug
// [2] Meeting
// [3] Report
// [4] Email
SplPriorityQueue — Priority Queues #
SplPriorityQueue is like SplMaxHeap but with value + priority pairs:
<?php
$pq = new SplPriorityQueue();
// insert(value, priority) — higher priority = more important
$pq->insert('regular task', 1);
$pq->insert('important task', 3);
$pq->insert('critical task', 5);
$pq->insert('urgent task', 4);
$pq->insert('low task', 0);
// Extraction always comes from the highest priority first
while (!$pq->isEmpty()) {
echo $pq->extract() . "\n";
}
// critical task
// urgent task
// important task
// regular task
// low task
// Extract mode — what's returned on extract/iterate
$pq2 = new SplPriorityQueue();
$pq2->setExtractFlags(SplPriorityQueue::EXTR_BOTH); // return value + priority
$pq2->insert('email', 2);
$pq2->insert('report', 5);
$item = $pq2->extract();
// ['data' => 'report', 'priority' => 5]
echo $item['data'] . " (priority: {$item['priority']})\n";
// SplPriorityQueue::EXTR_DATA — value only (default)
// SplPriorityQueue::EXTR_PRIORITY — priority only
// SplPriorityQueue::EXTR_BOTH — both as an array
// Real example: a task scheduler
class TaskScheduler
{
private SplPriorityQueue $queue;
public function __construct()
{
$this->queue = new SplPriorityQueue();
$this->queue->setExtractFlags(SplPriorityQueue::EXTR_BOTH);
}
public function add(string $name, int $priority, callable $handler): void
{
$this->queue->insert(
['name' => $name, 'handler' => $handler],
$priority
);
echo "Task '{$name}' added (priority: $priority)\n";
}
public function run(): void
{
while (!$this->queue->isEmpty()) {
$item = $this->queue->extract();
$task = $item['data'];
$prior = $item['priority'];
echo "Running [{$prior}]: {$task['name']}\n";
($task['handler'])();
}
}
}
SplFixedArray — Memory-Efficient Arrays #
SplFixedArray is a fixed-size array that can only store integers as indexes and uses far less memory than a regular PHP array:
<?php
// Regular array vs SplFixedArray — memory comparison
$n = 100_000;
$start = memory_get_usage();
$array = range(0, $n - 1);
$arrayMem = memory_get_usage() - $start;
$start = memory_get_usage();
$fixed = SplFixedArray::fromArray(range(0, $n - 1));
$fixedMem = memory_get_usage() - $start;
echo "Regular array: " . round($arrayMem / 1024 / 1024, 2) . " MB\n";
echo "SplFixedArray: " . round($fixedMem / 1024 / 1024, 2) . " MB\n";
// Regular array: ~8 MB, SplFixedArray: ~5 MB — ~40% more efficient
// Create an SplFixedArray
$fixed = new SplFixedArray(5); // capacity of 5 elements
$fixed[0] = 'a';
$fixed[1] = 'b';
$fixed[2] = 'c';
$fixed[3] = 'd';
$fixed[4] = 'e';
echo $fixed->getSize(); // 5
echo $fixed[2]; // c
// Iteration — can be foreach'd
foreach ($fixed as $i => $value) {
echo "$i: $value\n";
}
// From a regular array
$arr = [10, 20, 30, 40, 50];
$fixed = SplFixedArray::fromArray($arr);
$arr2 = $fixed->toArray(); // convert back
// Resize (possible but expensive — better to set the size from the start)
$fixed->setSize(10); // add 5 more slots (filled with null)
// When to use SplFixedArray:
// ✓ Large datasets with a known size in advance
// ✓ Numeric matrices or grids
// ✓ Data buffers with a fixed capacity
// ✗ Don't use it if the size changes often or you need string keys
// Example: a game grid or matrix
class Grid
{
private SplFixedArray $data;
public function __construct(
private int $width,
private int $height,
) {
$this->data = new SplFixedArray($width * $height);
}
public function set(int $x, int $y, mixed $value): void
{
$this->data[$y * $this->width + $x] = $value;
}
public function get(int $x, int $y): mixed
{
return $this->data[$y * $this->width + $x];
}
}
$grid = new Grid(100, 100); // a 100x100 grid = 10,000 cells
$grid->set(5, 3, 'X');
echo $grid->get(5, 3); // X
ArrayObject and ArrayIterator #
ArrayObject is an array that can be used as an object — it has all the array features but can also be extended and have methods:
<?php
// ArrayObject — an extensible array
$ao = new ArrayObject(['a' => 1, 'b' => 2, 'c' => 3]);
// Access like an array
$ao['d'] = 4;
echo $ao['a']; // 1
unset($ao['b']);
// Array methods
echo $ao->count(); // 3
$ao->append(5); // add an element at the end
$ao->offsetSet('e', 6); // set key => value
$ao->offsetGet('a'); // get by key
$ao->offsetExists('a'); // check key existence
$ao->offsetUnset('c'); // remove a key
// Iteration — can be foreach'd
foreach ($ao as $key => $val) {
echo "$key: $val\n";
}
// Sorting
$ao->uasort(fn($a, $b) => $b <=> $a); // sort descending, keep keys
// ArrayObject flags
$ao->setFlags(ArrayObject::ARRAY_AS_PROPS); // access keys as properties
$ao->name = 'Budi'; // $ao['name'] = 'Budi'
// ArrayIterator — like ArrayObject but lighter, can't be extended
$iter = new ArrayIterator(['x' => 10, 'y' => 20, 'z' => 30]);
$iter->rewind();
while ($iter->valid()) {
echo $iter->key() . ": " . $iter->current() . "\n";
$iter->next();
}
// Append, sorting
$iter->append(40);
$iter->asort(); // sort by value, keep keys
$iter->ksort(); // sort by key
// Example: a filterable collection
class FilterableCollection extends ArrayObject
{
public function filter(callable $predicate): static
{
return new static(
array_values(array_filter((array) $this, $predicate))
);
}
public function map(callable $transform): static
{
return new static(array_map($transform, (array) $this));
}
public function first(): mixed
{
return $this->count() > 0 ? $this->offsetGet(0) : null;
}
}
$collection = new FilterableCollection([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
$filtered = $collection
->filter(fn($n) => $n % 2 === 0) // take evens
->map(fn($n) => $n * 3); // multiply by 3
foreach ($filtered as $n) {
echo $n . " "; // 6 12 18 24 30
}
RecursiveIteratorIterator — Nested Traversal #
RecursiveIteratorIterator is the most elegant way to traverse nested data structures (trees, directories, nested arrays):
<?php
// Nested array traversal with RecursiveArrayIterator
$data = [
'frontend' => [
'html', 'css',
'javascript' => ['react', 'vue', 'angular'],
],
'backend' => [
'php' => ['laravel', 'symfony'],
'python' => ['django', 'flask'],
'node',
],
'database' => ['mysql', 'postgresql', 'mongodb'],
];
$iterator = new RecursiveIteratorIterator(
new RecursiveArrayIterator($data),
RecursiveIteratorIterator::SELF_FIRST // visit parents before children
);
foreach ($iterator as $key => $value) {
$indent = str_repeat(' ', $iterator->getDepth());
if (is_array($value)) {
echo $indent . "$key/\n";
} else {
echo $indent . "$key: $value\n";
}
}
// Recursive directory traversal — already covered in the IO article
// but this is the more idiomatic way
$dirIterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator('/var/www/app/src', RecursiveDirectoryIterator::SKIP_DOTS),
RecursiveIteratorIterator::LEAVES_ONLY // files only, not directories
);
foreach ($dirIterator as $file) {
if ($file->getExtension() === 'php') {
echo $file->getPathname() . "\n";
}
}
// RegexIterator — filter with regex
$phpFiles = new RegexIterator($dirIterator, '/\.php$/', RegexIterator::MATCH);
foreach ($phpFiles as $file) {
echo $file . "\n";
}
// FilterIterator — custom filters
class LargeFileIterator extends FilterIterator
{
public function __construct(Iterator $iterator, private int $minBytes = 1024)
{
parent::__construct($iterator);
}
public function accept(): bool
{
return $this->current()->isFile()
&& $this->current()->getSize() > $this->minBytes;
}
}
$largeFiles = new LargeFileIterator(
new RecursiveIteratorIterator(
new RecursiveDirectoryIterator('/var/www/app', RecursiveDirectoryIterator::SKIP_DOTS)
),
minBytes: 100 * 1024 // > 100KB
);
foreach ($largeFiles as $file) {
echo $file->getPathname() . " (" . round($file->getSize() / 1024, 1) . " KB)\n";
}
SplDoublyLinkedList — Two-Way Lists #
Both SplStack and SplQueue are built on top of SplDoublyLinkedList. You can use it directly when you need operations from both ends:
<?php
$dll = new SplDoublyLinkedList();
// Add to the front or back
$dll->push('back-1'); // add to the back
$dll->push('back-2');
$dll->unshift('front-1'); // add to the front
$dll->unshift('front-2');
// Peek without removing
echo $dll->top(); // "back-2" (the back)
echo $dll->bottom(); // "front-2" (the front)
// Remove from the front or back
echo $dll->pop(); // remove from the back
echo $dll->shift(); // remove from the front
// Access by index
echo $dll[0]; // first element (0-based)
echo $dll[$dll->count() - 1]; // last element
// Iteration modes
$dll->setIteratorMode(
SplDoublyLinkedList::IT_MODE_FIFO | // front to back
SplDoublyLinkedList::IT_MODE_KEEP // keep elements while iterating
);
// Alternatives: IT_MODE_LIFO | IT_MODE_DELETE (delete while iterating)
Summary #
SplStackfor LIFO — more expressive thanarray_push/array_pop. Useful for call stacks, undo history, expression parsing, and DFS traversal.SplQueuefor FIFO — more expressive thanarray_shift/array_push. Useful for simple job queues, BFS traversal, and sequentially placed buffers.SplMinHeap/SplMaxHeapalways maintain order without manual sorting — bothinsert()andextract()are O(log n). Useful for priority-based scheduling.SplPriorityQueueis like a heap but with separate value + priority pairs — usesetExtractFlags(EXTR_BOTH)to get both.SplFixedArrayis ~40% more memory-efficient than PHP arrays for fixed-size collections — great for matrices, grids, buffers, and large datasets whose size is known in advance.ArrayObjectcan be extended to create custom collections with additional methods — more OOP than a regular array but still usable like an array.RecursiveIteratorIterator+RecursiveArrayIterator/RecursiveDirectoryIteratoris the most elegant way to traverse nested structures — addRegexIteratororFilterIteratorfor more specific filtering.- Regular arrays remain the default choice — use SPL only when there’s a specific reason: clearer semantics (Stack/Queue), memory performance needs (FixedArray), or always-sorted requirements (Heap).