SPL

SPL #

Standard PHP Library (SPL) adalah kumpulan struktur data, iterator, dan antarmuka yang sudah ada di PHP sejak versi 5.0, tapi jarang digunakan karena banyak developer tidak tahu keberadaannya atau tidak tahu kapan menggunakannya. Padahal SPL menyediakan struktur data yang jauh lebih efisien dari array untuk use case tertentu: SplStack untuk LIFO yang benar (tidak bisa diakses secara acak seperti array), SplQueue untuk FIFO yang efisien, SplHeap untuk priority queue yang selalu terurut, dan SplFixedArray yang jauh lebih hemat memori dari array biasa untuk kumpulan data berukuran tetap. Artikel ini membahas kapan setiap struktur data SPL tepat digunakan dan bagaimana menggunakannya dengan benar.

Kapan Gunakan SPL vs Array Biasa #

flowchart TD
    A{Struktur data\napa yang dibutuhkan?} --> B{Urutan akses?}
    B -- "Acak berdasarkan indeks" --> C{Ukuran tetap?}
    C -- Ya --> D[SplFixedArray\nHemat memori ~40%]
    C -- Tidak --> E[Array biasa\nPaling fleksibel]

    B -- "LIFO — terakhir masuk\npertama keluar" --> F[SplStack\nSemantik yang jelas]
    B -- "FIFO — pertama masuk\npertama keluar" --> G[SplQueue\nEnqueue/dequeue efisien]
    B -- "Berdasarkan prioritas" --> H[SplMinHeap / SplMaxHeap\nSplPriorityQueue]
    B -- "Dua arah — depan & belakang" --> I[SplDoublyLinkedList]

    style D fill:#dcfce7
    style F fill:#dbeafe
    style G fill:#fef9c3
    style H fill:#f3e8ff

SplStack — Last In First Out #

SplStack adalah implementasi stack yang benar — hanya bisa push ke atas dan pop dari atas. Berbeda dari menggunakan array sebagai stack, SplStack memberikan semantik yang jelas dan tidak bisa diakses secara acak:

<?php
$stack = new SplStack();

// push — tambah ke atas
$stack->push('pertama');
$stack->push('kedua');
$stack->push('ketiga');

echo $stack->count(); // 3

// top — lihat yang paling atas tanpa menghapus
echo $stack->top(); // "ketiga"

// pop — ambil dari atas (LIFO)
echo $stack->pop(); // "ketiga"
echo $stack->pop(); // "kedua"
echo $stack->pop(); // "pertama"

// isEmpty — cek apakah kosong
var_dump($stack->isEmpty()); // bool(true)

// Iterasi — SplStack bisa di-foreach (LIFO order)
$stack->push('a');
$stack->push('b');
$stack->push('c');

foreach ($stack as $nilai) {
    echo $nilai . "\n"; // c, b, a (LIFO)
}

// Contoh nyata: undo/redo history
class EditorHistory
{
    private SplStack $undoStack;
    private SplStack $redoStack;

    public function __construct()
    {
        $this->undoStack = new SplStack();
        $this->redoStack = new SplStack();
    }

    public function lakukan(string $aksi): void
    {
        $this->undoStack->push($aksi);
        // Setelah aksi baru, redo history dihapus
        while (!$this->redoStack->isEmpty()) {
            $this->redoStack->pop();
        }
        echo "Lakukan: $aksi\n";
    }

    public function undo(): ?string
    {
        if ($this->undoStack->isEmpty()) {
            echo "Tidak ada yang bisa di-undo\n";
            return null;
        }
        $aksi = $this->undoStack->pop();
        $this->redoStack->push($aksi);
        echo "Undo: $aksi\n";
        return $aksi;
    }

    public function redo(): ?string
    {
        if ($this->redoStack->isEmpty()) {
            echo "Tidak ada yang bisa di-redo\n";
            return null;
        }
        $aksi = $this->redoStack->pop();
        $this->undoStack->push($aksi);
        echo "Redo: $aksi\n";
        return $aksi;
    }
}

$editor = new EditorHistory();
$editor->lakukan("Tulis 'Halo'");
$editor->lakukan("Bold text");
$editor->lakukan("Ubah warna merah");
$editor->undo(); // Undo: Ubah warna merah
$editor->undo(); // Undo: Bold text
$editor->redo(); // Redo: Bold text

SplQueue — First In First Out #

SplQueue adalah implementasi antrian yang efisien. Enqueue di belakang, dequeue dari depan:

<?php
$antrian = new SplQueue();

// enqueue — tambah ke belakang antrian
$antrian->enqueue('tugas-1');
$antrian->enqueue('tugas-2');
$antrian->enqueue('tugas-3');

echo $antrian->count(); // 3

// bottom — lihat yang paling depan tanpa menghapus
echo $antrian->bottom(); // "tugas-1"

// top — lihat yang paling belakang
echo $antrian->top(); // "tugas-3"

// dequeue — ambil dari depan (FIFO)
echo $antrian->dequeue(); // "tugas-1"
echo $antrian->dequeue(); // "tugas-2"

// Iterasi — SplQueue bisa di-foreach (FIFO order)
$antrian->enqueue('x');
$antrian->enqueue('y');
foreach ($antrian as $item) {
    echo $item . "\n"; // tugas-3, x, y (FIFO)
}

// Mode iterasi
$antrian->setIteratorMode(SplDoublyLinkedList::IT_MODE_DELETE); // hapus saat iterasi
foreach ($antrian as $item) {
    prosesItem($item); // setiap item diproses dan dihapus dari antrian
}
// Antrian sekarang kosong

// Contoh nyata: job queue sederhana
class SimpleJobQueue
{
    private SplQueue $queue;

    public function __construct()
    {
        $this->queue = new SplQueue();
        $this->queue->setIteratorMode(SplDoublyLinkedList::IT_MODE_FIFO);
    }

    public function tambahJob(array $job): void
    {
        $this->queue->enqueue($job);
        echo "Job ditambahkan: {$job['type']}\n";
    }

    public function prosesSemuaJob(): void
    {
        while (!$this->queue->isEmpty()) {
            $job = $this->queue->dequeue();
            echo "Memproses: {$job['type']}\n";
            // proses job...
        }
    }

    public function jumlahJob(): int
    {
        return $this->queue->count();
    }
}

$jobQueue = new SimpleJobQueue();
$jobQueue->tambahJob(['type' => 'kirim_email', 'to' => '[email protected]']);
$jobQueue->tambahJob(['type' => 'resize_gambar', 'file' => 'foto.jpg']);
$jobQueue->tambahJob(['type' => 'buat_laporan', 'bulan' => 3]);
$jobQueue->prosesSemuaJob();

SplMinHeap dan SplMaxHeap — Priority Queue Dasar #

Heap selalu mempertahankan elemen dalam urutan — SplMinHeap selalu menaruh nilai terkecil di atas, SplMaxHeap nilai terbesar di atas:

<?php
// SplMinHeap — nilai terkecil di atas
$minHeap = new SplMinHeap();
$minHeap->insert(5);
$minHeap->insert(1);
$minHeap->insert(8);
$minHeap->insert(3);

echo $minHeap->top(); // 1 — terkecil selalu di atas

while (!$minHeap->isEmpty()) {
    echo $minHeap->extract() . " "; // 1 3 5 8 — ascending order!
}

// SplMaxHeap — nilai terbesar di atas
$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() untuk objek
class TaskHeap extends SplMinHeap
{
    protected function compare(mixed $a, mixed $b): int
    {
        // compare() harus return > 0 jika $a harus di atas $b
        // Untuk MinHeap: prioritas lebih kecil = lebih penting = di atas
        // Kita ingin prioritas kecil di atas, jadi balik perbandingannya
        return $b['prioritas'] <=> $a['prioritas'];
    }
}

$taskHeap = new TaskHeap();
$taskHeap->insert(['nama' => 'Laporan',   'prioritas' => 3]);
$taskHeap->insert(['nama' => 'Bug kritis', 'prioritas' => 1]);
$taskHeap->insert(['nama' => 'Meeting',   'prioritas' => 2]);
$taskHeap->insert(['nama' => 'Email',     'prioritas' => 4]);

while (!$taskHeap->isEmpty()) {
    $task = $taskHeap->extract();
    echo "[{$task['prioritas']}] {$task['nama']}\n";
}
// [1] Bug kritis
// [2] Meeting
// [3] Laporan
// [4] Email

SplPriorityQueue — Antrian Berprioritas #

SplPriorityQueue seperti SplMaxHeap tapi dengan pasangan nilai + prioritas:

<?php
$pq = new SplPriorityQueue();

// insert(nilai, prioritas) — prioritas lebih besar = lebih penting
$pq->insert('tugas biasa',    1);
$pq->insert('tugas penting',  3);
$pq->insert('tugas kritis',   5);
$pq->insert('tugas mendesak', 4);
$pq->insert('tugas rendah',   0);

// Ekstrak selalu dari prioritas tertinggi dulu
while (!$pq->isEmpty()) {
    echo $pq->extract() . "\n";
}
// tugas kritis
// tugas mendesak
// tugas penting
// tugas biasa
// tugas rendah

// Mode ekstrak — apa yang dikembalikan saat extract/iterate
$pq2 = new SplPriorityQueue();
$pq2->setExtractFlags(SplPriorityQueue::EXTR_BOTH); // kembalikan nilai + prioritas
$pq2->insert('email', 2);
$pq2->insert('laporan', 5);

$item = $pq2->extract();
// ['data' => 'laporan', 'priority' => 5]
echo $item['data'] . " (prioritas: {$item['priority']})\n";

// SplPriorityQueue::EXTR_DATA     — hanya nilai (default)
// SplPriorityQueue::EXTR_PRIORITY — hanya prioritas
// SplPriorityQueue::EXTR_BOTH     — keduanya sebagai array

// Contoh nyata: task scheduler
class TaskScheduler
{
    private SplPriorityQueue $queue;

    public function __construct()
    {
        $this->queue = new SplPriorityQueue();
        $this->queue->setExtractFlags(SplPriorityQueue::EXTR_BOTH);
    }

    public function tambah(string $nama, int $prioritas, callable $handler): void
    {
        $this->queue->insert(
            ['nama' => $nama, 'handler' => $handler],
            $prioritas
        );
        echo "Task '{$nama}' ditambahkan (prioritas: $prioritas)\n";
    }

    public function jalankan(): void
    {
        while (!$this->queue->isEmpty()) {
            $item    = $this->queue->extract();
            $task    = $item['data'];
            $prior   = $item['priority'];
            echo "Menjalankan [{$prior}]: {$task['nama']}\n";
            ($task['handler'])();
        }
    }
}

SplFixedArray — Array Hemat Memori #

SplFixedArray adalah array dengan ukuran tetap yang hanya bisa menyimpan integer sebagai index dan menggunakan memori jauh lebih sedikit dari array PHP biasa:

<?php
// Array biasa vs SplFixedArray — perbandingan memori
$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 "Array biasa: " . round($arrayMem / 1024 / 1024, 2) . " MB\n";
echo "SplFixedArray: " . round($fixedMem / 1024 / 1024, 2) . " MB\n";
// Array biasa: ~8 MB, SplFixedArray: ~5 MB — ~40% lebih hemat

// Buat SplFixedArray
$fixed = new SplFixedArray(5);  // kapasitas 5 elemen
$fixed[0] = 'a';
$fixed[1] = 'b';
$fixed[2] = 'c';
$fixed[3] = 'd';
$fixed[4] = 'e';

echo $fixed->getSize(); // 5
echo $fixed[2];         // c

// Iterasi — bisa di-foreach
foreach ($fixed as $i => $nilai) {
    echo "$i: $nilai\n";
}

// Dari array biasa
$arr   = [10, 20, 30, 40, 50];
$fixed = SplFixedArray::fromArray($arr);
$arr2  = $fixed->toArray(); // konversi kembali

// Resize (bisa diperbesar tapi mahal — lebih baik tentukan ukuran dari awal)
$fixed->setSize(10); // tambah 5 slot lagi (diisi null)

// Kapan gunakan SplFixedArray:
// ✓ Dataset besar dengan ukuran yang diketahui sebelumnya
// ✓ Matrix numerik atau grid
// ✓ Buffer data dengan kapasitas tetap
// ✗ Jangan gunakan jika ukuran berubah sering atau butuh key string

// Contoh: grid game atau matriks
class Grid
{
    private SplFixedArray $data;

    public function __construct(
        private int $lebar,
        private int $tinggi,
    ) {
        $this->data = new SplFixedArray($lebar * $tinggi);
    }

    public function set(int $x, int $y, mixed $nilai): void
    {
        $this->data[$y * $this->lebar + $x] = $nilai;
    }

    public function get(int $x, int $y): mixed
    {
        return $this->data[$y * $this->lebar + $x];
    }
}

$grid = new Grid(100, 100); // grid 100x100 = 10.000 sel
$grid->set(5, 3, 'X');
echo $grid->get(5, 3); // X

ArrayObject dan ArrayIterator #

ArrayObject adalah array yang bisa digunakan sebagai objek — memiliki semua fitur array tapi juga bisa di-extend dan memiliki method:

<?php
// ArrayObject — array yang bisa di-extend
$ao = new ArrayObject(['a' => 1, 'b' => 2, 'c' => 3]);

// Akses seperti array
$ao['d'] = 4;
echo $ao['a']; // 1
unset($ao['b']);

// Method array
echo $ao->count();           // 3
$ao->append(5);              // tambah elemen di akhir
$ao->offsetSet('e', 6);     // set key => value
$ao->offsetGet('a');        // get by key
$ao->offsetExists('a');     // cek keberadaan key
$ao->offsetUnset('c');      // hapus key

// Iterasi — bisa di-foreach
foreach ($ao as $key => $val) {
    echo "$key: $val\n";
}

// Sorting
$ao->uasort(fn($a, $b) => $b <=> $a); // sort descending, pertahankan key

// Flag ArrayObject
$ao->setFlags(ArrayObject::ARRAY_AS_PROPS); // akses key sebagai properti
$ao->nama = 'Budi'; // $ao['nama'] = 'Budi'

// ArrayIterator — seperti ArrayObject tapi lebih ringan, tidak bisa di-extend
$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, pertahankan key
$iter->ksort(); // sort by key

// Contoh: koleksi yang bisa difilter
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;
    }
}

$koleksi = new FilterableCollection([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
$hasilFilter = $koleksi
    ->filter(fn($n) => $n % 2 === 0) // ambil genap
    ->map(fn($n) => $n * 3);          // kalikan 3

foreach ($hasilFilter as $n) {
    echo $n . " "; // 6 12 18 24 30
}

RecursiveIteratorIterator — Traversal Nested #

RecursiveIteratorIterator adalah cara paling elegan untuk traversal struktur data bersarang (pohon, direktori, nested array):

<?php
// Traversal array nested dengan 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 // kunjungi parent sebelum 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";
    }
}

// Traversal direktori rekursif — sudah dibahas di artikel IO
// tapi ini cara yang lebih idiomatik
$dirIterator = new RecursiveIteratorIterator(
    new RecursiveDirectoryIterator('/var/www/app/src', RecursiveDirectoryIterator::SKIP_DOTS),
    RecursiveIteratorIterator::LEAVES_ONLY // hanya file, bukan direktori
);

foreach ($dirIterator as $file) {
    if ($file->getExtension() === 'php') {
        echo $file->getPathname() . "\n";
    }
}

// RegexIterator — filter dengan regex
$phpFiles = new RegexIterator($dirIterator, '/\.php$/', RegexIterator::MATCH);
foreach ($phpFiles as $file) {
    echo $file . "\n";
}

// FilterIterator — filter kustom
class UkuranBesarIterator 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;
    }
}

$fileBesar = new UkuranBesarIterator(
    new RecursiveIteratorIterator(
        new RecursiveDirectoryIterator('/var/www/app', RecursiveDirectoryIterator::SKIP_DOTS)
    ),
    minBytes: 100 * 1024 // > 100KB
);

foreach ($fileBesar as $file) {
    echo $file->getPathname() . " (" . round($file->getSize() / 1024, 1) . " KB)\n";
}

SplDoublyLinkedList — Daftar Dua Arah #

SplStack dan SplQueue keduanya dibangun di atas SplDoublyLinkedList. Kamu bisa menggunakannya langsung jika butuh operasi dari kedua ujung:

<?php
$dll = new SplDoublyLinkedList();

// Tambah ke depan atau belakang
$dll->push('belakang-1');     // tambah ke belakang
$dll->push('belakang-2');
$dll->unshift('depan-1');    // tambah ke depan
$dll->unshift('depan-2');

// Lihat tanpa hapus
echo $dll->top();    // "belakang-2" (paling belakang)
echo $dll->bottom(); // "depan-2" (paling depan)

// Hapus dari depan atau belakang
echo $dll->pop();    // hapus dari belakang
echo $dll->shift();  // hapus dari depan

// Akses by index
echo $dll[0]; // elemen pertama (0-based)
echo $dll[$dll->count() - 1]; // elemen terakhir

// Mode iterasi
$dll->setIteratorMode(
    SplDoublyLinkedList::IT_MODE_FIFO |        // depan ke belakang
    SplDoublyLinkedList::IT_MODE_KEEP          // pertahankan elemen saat iterasi
);
// Alternatif: IT_MODE_LIFO | IT_MODE_DELETE (hapus saat iterasi)

Ringkasan #

  • SplStack untuk LIFO — lebih ekspresif dari array_push/array_pop. Berguna untuk call stack, undo history, parsing ekspresi, dan DFS traversal.
  • SplQueue untuk FIFO — lebih ekspresif dari array_shift/array_push. Berguna untuk job queue sederhana, BFS traversal, dan buffer yang ditempatkan secara berurutan.
  • SplMinHeap/SplMaxHeap selalu mempertahankan urutan tanpa perlu sorting manual — insert() dan extract() keduanya O(log n). Berguna untuk scheduling berdasarkan prioritas.
  • SplPriorityQueue seperti heap tapi dengan pasangan nilai + prioritas yang terpisah — gunakan setExtractFlags(EXTR_BOTH) untuk mendapatkan keduanya.
  • SplFixedArray ~40% lebih hemat memori dari array PHP untuk kumpulan data berukuran tetap — cocok untuk matrix, grid, buffer, dan dataset besar yang ukurannya diketahui sejak awal.
  • ArrayObject bisa di-extend untuk membuat koleksi kustom dengan method tambahan — lebih OOP dari array biasa tapi tetap bisa digunakan seperti array.
  • RecursiveIteratorIterator + RecursiveArrayIterator/RecursiveDirectoryIterator adalah cara paling elegan untuk traversal struktur bersarang — tambahkan RegexIterator atau FilterIterator untuk filter yang lebih spesifik.
  • Array biasa tetap pilihan default — gunakan SPL hanya ketika ada alasan spesifik: semantik yang lebih jelas (Stack/Queue), kebutuhan performa memori (FixedArray), atau kebutuhan selalu-terurut (Heap).

← Sebelumnya: Image   Berikutnya: Zip →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact