Aplikasi nyata jarang berurusan dengan berkas tunggal — proyek PHP adalah pohon direktori berisi kode, vendor, cache, dan file upload yang harus dibuat, discan, dan dibersihkan secara programatik. PHP menyediakan dua lapis API untuk kebutuhan ini: fungsi prosedural seperti mkdir(), rmdir(), dan scandir(), serta iterator berorientasi objek seperti DirectoryIterator dan RecursiveDirectoryIterator yang elegan untuk penelusuran. Pada chapter ini kita mempelajari keduanya dari bawah ke atas, ditutup dengan chdir() untuk berpindah direktori kerja. Semua contoh membangun struktur latihan sendiri lalu membersihkannya sepenuhnya di akhir skrip — membersihkan pohon direktori memang akan menjadi salah satu keterampilan yang kita latih.
Membuat dan Menghapus Direktori #
Fungsi mkdir() membuat direktori baru, rmdir() menghapus direktori, dan is_dir() memeriksa apakah sebuah path adalah direktori (bukan berkas biasa). Ketiganya bekerja pada satu level saja — rmdir() menolak menghapus direktori yang masih berisi:
<?php
$direktori = __DIR__ . '/penyimpanan';
if (!is_dir($direktori)) {
mkdir($direktori);
echo "Direktori dibuat.\n";
}
var_dump(is_dir($direktori));
rmdir($direktori);
var_dump(is_dir($direktori));Output yang Dihasilkan:
Direktori dibuat.
bool(true)
bool(false)Pemeriksaan !is_dir() sebelum mkdir() mengikuti pola yang sama dengan is_file() pada chapter sebelumnya: membuat operasi aman untuk dijalankan berulang kali tanpa memicu Warning “File exists”. Setelah rmdir() berhasil, is_dir() kembali false — direktori benar-benar hilang dari sistem berkas.
Membuat Struktur Bertingkat secara Rekursif #
Membuat proyek/src/Controller dengan panggilan mkdir() biasa akan gagal karena direktori induk proyek/src belum ada. Parameter ketiga mkdir() — $recursive — memerintahkan PHP membuat seluruh rantai induk yang diperlukan sekaligus:
<?php
$target = __DIR__ . '/proyek/src/Controller';
mkdir($target, 0777, true);
echo is_dir($target) ? "Struktur berhasil dibuat.\n" : "Struktur gagal dibuat.\n";
echo "Permission direktori baru: " . sprintf('%04o', fileperms($target) & 0777) . "\n";
// rmdir hanya menghapus direktori kosong: bersihkan dari dalam ke luar
rmdir(__DIR__ . '/proyek/src/Controller');
rmdir(__DIR__ . '/proyek/src');
rmdir(__DIR__ . '/proyek');
echo "Struktur berhasil dibersihkan.\n";Output yang Dihasilkan:
Struktur berhasil dibuat.
Permission direktori baru: 0775
Struktur berhasil dibersihkan.Parameter kedua 0777 adalah permission dasar untuk direktori baru — sebagaimana dibahas pada chapter File Permissions, umask kemudian mencabut bit darinya, sehingga dengan umask 0002 direktori lahir dengan 0775 alih-alih 0777. Pembersihan dilakukan tiga kali rmdir() dari level terdalam ke atas karena rmdir() menolak direktori yang tidak kosong — pola “dalam ke luar” ini yang nantinya diotomatisasi oleh iterator rekursif.
Membaca Isi Direktori dengan scandir #
Fungsi scandir() mengembalikan array berisi seluruh entri sebuah direktori, sudah terurut secara alfabetis. Satu kejutan bagi pemula: hasilnya selalu memuat dua entri khusus . (direktori ini) dan .. (direktori induk) yang biasanya perlu disaring:
<?php
$direktori = __DIR__ . '/galeri';
mkdir($direktori);
file_put_contents($direktori . '/foto-kantor.txt', 'foto 1');
file_put_contents($direktori . '/foto-liburan.txt', 'foto 2');
$semuaEntri = scandir($direktori);
print_r($semuaEntri);
$berkasSaja = array_values(array_diff($semuaEntri, ['.', '..']));
print_r($berkasSaja);
foreach ($berkasSaja as $berkas) {
unlink("$direktori/$berkas");
}
rmdir($direktori);Output yang Dihasilkan:
Array
(
[0] => .
[1] => ..
[2] => foto-kantor.txt
[3] => foto-liburan.txt
)
Array
(
[0] => foto-kantor.txt
[1] => foto-liburan.txt
)Mari kita bedah dua baris kuncinya: array_diff($semuaEntri, ['.', '..']) mengeluarkan kedua entri khusus dari daftar, dan array_values() membangkitkan ulang penomoran array — tanpa array_values(), key tetap 2 dan 3 karena array_diff() mempertahankan key asli, seperti terlihat pada hasil print_r() pertama. Perhatikan juga bahwa hasil scandir() memuat nama entri saja, bukan path lengkap — untuk memanipulasi berkasnya kita merangkai "$direktori/$berkas" sendiri.
Iterasi Objek dengan DirectoryIterator #
DirectoryIterator adalah iterator SPL yang memodelkan isi direktori sebagai kumpulan objek. Setiap objek entri membawa informasi lengkap — nama, jenis, ukuran, timestamp — dan method isDot() dengan mudah mengenali entri . dan ..:
<?php
$direktori = __DIR__ . '/arsip';
mkdir($direktori);
file_put_contents($direktori . '/catatan.txt', 'isi');
mkdir($direktori . '/cadangan');
$entri = [];
foreach (new DirectoryIterator($direktori) as $item) {
if ($item->isDot()) {
continue;
}
$jenis = $item->isDir() ? 'direktori' : 'berkas';
$entri[] = "$jenis: " . $item->getFilename();
}
sort($entri);
echo implode("\n", $entri) . "\n";
rmdir($direktori . '/cadangan');
unlink($direktori . '/catatan.txt');
rmdir($direktori);Output yang Dihasilkan:
berkas: catatan.txt
direktori: cadanganMethod getFilename() mengembalikan nama entri, isDir() membedakan direktori dari berkas, dan isDot() menyaring dua entri khusus tanpa perlu membandingkan string manual. Berbeda dengan scandir() yang selalu terurut alfabetis, DirectoryIterator mengikuti urutan internal sistem berkas — kode di atas sengaja mengumpulkan hasil ke array $entri lalu sort() agar output selalu terurut dan deterministik. Bandingkan fleksibilitasnya dengan scandir(): iterator memberi akses metode-metode kaya (getSize(), getMTime(), getExtension()) tanpa panggilan stat terpisah per entri.
Mencari File dengan Pola glob #
Ketika yang dibutuhkan adalah “semua berkas yang cocok dengan pola”, glob() mengambil alih pekerjaan: fungsi ini menerima wildcard gaya shell seperti *.txt atau laporan-*.csv, lalu mengembalikan array path lengkap yang cocok:
<?php
$direktori = __DIR__ . '/dokumen';
mkdir($direktori);
foreach (['laporan-2024.txt', 'laporan-2025.txt', 'foto-liburan.jpg'] as $nama) {
file_put_contents("$direktori/$nama", 'isi');
}
foreach (glob($direktori . '/laporan-*.txt') as $berkas) {
echo basename($berkas) . "\n";
}
$tidakAda = glob($direktori . '/*.pdf');
echo "Berkas PDF ditemukan: " . count($tidakAda) . "\n";
foreach (glob($direktori . '/*') as $berkas) {
unlink($berkas);
}
rmdir($direktori);Output yang Dihasilkan:
laporan-2024.txt
laporan-2025.txt
Berkas PDF ditemukan: 0Pola laporan-*.txt hanya mencocokkan dua berkas laporan meskipun direktori juga berisi foto-liburan.jpg. Ketika tidak ada yang cocok, glob() mengembalikan array kosong — berbeda dengan banyak fungsi filesystem lain yang mengembalikan false saat gagal — sehingga count() langsung aman dipakai. Perhatikan juga basename() yang memangkas bagian direktori dari path hasil glob() (yang selalu berupa path lengkap), sehingga output hanya memuat nama berkasnya.
Menelusuri Direktori Secara Rekursif #
Semua alat sejauh ini bekerja pada satu level direktori. Untuk menelusuri pohon bertingkat, SPL menyediakan RecursiveDirectoryIterator yang tahu cara “turun” ke subdirektorinya, dibungkus RecursiveIteratorIterator yang mengatur urutan penelusurannya. Contoh berikut adalah contoh lengkap sekaligus utilitas nyata: membangun struktur proyek, menelusurinya, lalu membersihkan seluruh pohon:
<?php
function hapusDirektori(string $path): void
{
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($path, FilesystemIterator::SKIP_DOTS),
RecursiveIteratorIterator::CHILD_FIRST
);
foreach ($iterator as $item) {
if ($item->isDir()) {
rmdir($item->getPathname());
} else {
unlink($item->getPathname());
}
}
rmdir($path);
}
$akar = __DIR__ . '/proyek-latihan';
mkdir($akar . '/src/Model', 0777, true);
mkdir($akar . '/uji', 0777, true);
file_put_contents($akar . '/src/index.php', "<?php // titik masuk\n");
file_put_contents($akar . '/src/Model/Pengguna.php', "<?php // entitas\n");
file_put_contents($akar . '/uji/ContohUji.php', "<?php // uji coba\n");
$baris = [];
$penelusur = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($akar, FilesystemIterator::SKIP_DOTS),
RecursiveIteratorIterator::SELF_FIRST
);
foreach ($penelusur as $item) {
$jenis = $item->isDir() ? 'direktori' : 'berkas';
$baris[] = $jenis . ': ' . ltrim(str_replace($akar, '', $item->getPathname()), '/');
}
sort($baris);
echo implode("\n", $baris) . "\n";
hapusDirektori($akar);
var_dump(is_dir($akar));Output yang Dihasilkan:
berkas: src/Model/Pengguna.php
berkas: src/index.php
berkas: uji/ContohUji.php
direktori: src
direktori: src/Model
direktori: uji
bool(false)Ada tiga poin kunci pada contoh ini:
FilesystemIterator::SKIP_DOTS: memberi tahu iterator untuk melewati entri.dan..secara otomatis — tanpa flag ini, penelusuran rekursif akan tersangkut berputar di direktori induk.- Mode
SELF_FIRSTvsCHILD_FIRST: penelusuran pertama memakaiSELF_FIRSTagar direktori muncul sebelum isinya (nyaman untuk menampilkan struktur). FungsihapusDirektori()memakaiCHILD_FIRST— kebalikannya — agar isi dihapus lebih dulu, dan setiap direktori sudah pasti kosong saat gilirannya tiba; inilah solusi otomatis dari pola “dalam ke luar” yang kita lakukan manual sebelumnya. getPathname(): mengembalikan path lengkap setiap entri;str_replace($akar, '', ...)hanya mengubahnya menjadi path relatif agar tampilan output rapi.
Berpindah Direktori Kerja dengan chdir dan getcwd #
Terakhir, PHP bisa memindahkan current working directory (cwd) dari prosesnya sendiri dengan chdir(), dan melihat lokasinya dengan getcwd(). Path relatif yang Anda tulis di kode — seperti 'catatan.txt' — selalu diresolve terhadap cwd ini:
<?php
$direktori = __DIR__ . '/workspace';
mkdir($direktori);
file_put_contents($direktori . '/catatan.txt', 'berkas di dalam workspace');
echo 'cwd awal: ' . basename(getcwd()) . "\n";
chdir($direktori);
echo 'cwd baru: ' . basename(getcwd()) . "\n";
echo file_get_contents('catatan.txt') . "\n";
chdir('..');
unlink($direktori . '/catatan.txt');
rmdir($direktori);Output yang Dihasilkan:
cwd awal: directory-operations
cwd baru: workspace
berkas di dalam workspaceSetelah chdir($direktori), panggilan file_get_contents('catatan.txt') berhasil tanpa path lengkap karena cwd sudah pindah — itulah kekuatan sekaligus bahaya cwd: artinya bergantung pada lokasi eksekusi. Contoh ini memakai basename() untuk menampilkan hanya nama direktorinya, karena getcwd() selalu mengembalikan path absolut yang panjang dan berbeda-beda antar mesin.
Ringkasan #
Kini kita menguasai lapisan prosedural (mkdir() rekursif, rmdir() yang menuntut direktori kosong, scandir() dengan entri . dan ..), lapisan objek (DirectoryIterator dengan API kaya per entri), pencarian berpola (glob()), penelusuran pohon (RecursiveDirectoryIterator dengan mode SELF_FIRST dan CHILD_FIRST untuk membersihkan direktori), hingga manajemen cwd (chdir()/getcwd()). Dengan bekal operasi berkas dan direktori ini, kita siap naik ke abstraksi yang menyatukan semuanya: streams — mekanisme di balik fopen() yang juga menggerakkan jaringan, memori, dan berkas terkompresi.