Job terjadwal dan tool CLI jarang berhenti di mencetak teks — kebanyakan dari mereka hidup untuk mengolah berkas: menyeragamkan nama ribuan foto, merotasi backup setiap malam, membersihkan cache yang kedaluwarsa, lalu melaporkan hasilnya. PHP memilik seluruh peralatan untuk pekerjaan ini: rename(), unlink(), touch(), filemtime(), hingga iterator direktori. Pada chapter ini kita akan membangun empat pola otomasi filesystem yang paling sering dibutuhkan, semuanya diuji pada arena uji di /tmp yang aman untuk dijalankan berulang kali.
Arena Uji yang Dapat Dijalankan Ulang #
Otomasi filesystem berbahaya bila salah sasaran, jadi setiap contoh chapter ini memakai pola yang sama: sebuah direktori khusus di /tmp yang direset setiap awal eksekusi, lalu diisi data contoh yang sama. Dengan begitu kode dapat dijalankan seratus kali pun hasilnya identik — dan Anda bisa membedakan antara data uji dan berkas sungguhan:
<?php
$dir = "/tmp/fsauto/contoh";
if (!is_dir($dir)) {
mkdir($dir, 0777, true);
}
foreach (glob("$dir/*") as $lama) {
unlink($lama); // reset arena uji
}Pola tiga baris ini — pastikan direktori ada, hapus isinya, lalu isi ulang — akan muncul di setiap contoh berikutnya. Di tool produksi, ganti jalurnya dengan parameter dan selalu sediakan mode dry-run seperti yang kita bahas di bawah.
Ganti Nama Massal #
Berkas dari kamera, pengguna, atau sistem lawas datang dengan nama sembarangan: campuran huruf besar, spasi, dan tanda kurung. Skrip berikut menyeragamkan semua nama di sebuah direktori menjadi huruf kecil yang dipisah tanda hubung:
<?php
$dir = "/tmp/fsauto/ganti-nama";
if (!is_dir($dir)) {
mkdir($dir, 0777, true);
}
foreach (glob("$dir/*") as $lama) {
unlink($lama); // reset arena uji
}
foreach (["Laporan Q1 FINAL.pdf", "Notulen Rapat (Draft).txt", "FOTO produk.JPG"] as $nama) {
file_put_contents("$dir/$nama", "isi contoh\n");
}
foreach (scandir($dir) as $nama) {
if (in_array($nama, [".", ".."], true)) {
continue;
}
$baru = strtolower($nama);
$baru = str_replace(["(", ")"], "", $baru);
$baru = preg_replace("/[^a-z0-9.]+/", "-", $baru);
$baru = trim($baru, "-");
if ($baru !== $nama) {
rename("$dir/$nama", "$dir/$baru");
echo "$nama -> $baru\n";
}
}Output yang Dihasilkan:
FOTO produk.JPG -> foto-produk.jpg
Laporan Q1 FINAL.pdf -> laporan-q1-final.pdf
Notulen Rapat (Draft).txt -> notulen-rapat-draft.txtPembentukan nama baru berjalan dalam empat tahap: strtolower() menyeragamkan huruf, str_replace() membuang tanda kurung, preg_replace() mengganti setiap rentang karakter selain huruf-angka-titik dengan satu tanda hubung, dan trim() membereskan ujungnya. Perhatikan bahwa titik tetap diizinkan sehingga ekstensi .pdf selamat, dan proses hanya menimpa nama lama bila hasilnya memang berbeda. Fungsi scandir() mengembalikan isi direktori terurut menaik, itulah urutan baris pada output di atas.
Rotasi Arsip #
Backup yang menumpuk tanpa batas akhirnya menghabiskan disk. Pola rotasi menyimpan hanya N arsip terbaru dan menghapus sisanya. Karena arsip backup lazim memuat tanggal pada namanya, urutan nama sekaligus menjadi urutan waktu:
<?php
$dir = "/tmp/fsauto/rotasi";
if (!is_dir($dir)) {
mkdir($dir, 0777, true);
}
foreach (glob("$dir/*") as $lama) {
unlink($lama);
}
foreach (["2026-01-05", "2026-01-08", "2026-01-11", "2026-01-14", "2026-01-17"] as $tanggal) {
file_put_contents("$dir/backup-$tanggal.tar.gz", "data $tanggal\n");
}
const SIMPAN = 3; // jumlah arsip yang dipertahankan
$arsip = array_values(preg_grep("/^backup-/", scandir($dir)));
sort($arsip); // nama memuat tanggal: urutan nama = urutan waktu
$kelebihan = max(0, count($arsip) - SIMPAN);
foreach (array_slice($arsip, 0, $kelebihan) as $hapus) {
unlink("$dir/$hapus");
echo "dihapus: $hapus\n";
}
$sisa = count(preg_grep("/^backup-/", scandir($dir)));
echo "arsip tersisa: $sisa\n";Output yang Dihasilkan:
dihapus: backup-2026-01-05.tar.gz
dihapus: backup-2026-01-08.tar.gz
arsip tersisa: 3Lima arsip disemai, SIMPAN menetapkan tiga yang boleh tinggal, sehingga dua arsip tertua ikut array_slice() dan dihapus. Bila arsip Anda tidak memuat tanggal pada nama, ganti sort() dengan pengurutan berdasarkan filemtime() — prinsipnya sama: urutkan dari yang paling tua, hapus kelebihannya. Rotasi seperti ini berpasangan alami dengan job cron harian dari chapter sebelumnya.
Membersihkan Berkas Berdasarkan Umur #
Cache dan berkas sementara sebaiknya punya umur simpan. PHP membaca waktu modifikasi terakhir lewat filemtime(), dan yang menarik untuk pengujian: touch() bisa menulis waktu itu juga — memungkinkan kita membekukan umur berkas secara presisi:
<?php
$dir = "/tmp/fsauto/cache";
if (!is_dir($dir)) {
mkdir($dir, 0777, true);
}
foreach (glob("$dir/*") as $lama) {
unlink($lama);
}
// "Sekarang" dibekukan supaya contoh deterministik.
$kini = new DateTimeImmutable("2026-01-15 12:00:00", new DateTimeZone("Asia/Jakarta"));
foreach (["segar" => 3, "hangat" => 10, "kedaluwarsa" => 40] as $nama => $umurHari) {
$path = "$dir/$nama.tmp";
file_put_contents($path, "data\n");
touch($path, $kini->getTimestamp() - $umurHari * 86400); // bekukan mtime
}
$batasDetik = 30 * 86400; // lebih tua dari 30 hari = dihapus
foreach (scandir($dir) as $nama) {
if (!str_ends_with($nama, ".tmp")) {
continue;
}
$path = "$dir/$nama";
if ($kini->getTimestamp() - filemtime($path) > $batasDetik) {
unlink($path);
echo "dihapus: $nama\n";
} else {
echo "disimpan: $nama\n";
}
}Output yang Dihasilkan:
disimpan: hangat.tmp
dihapus: kedaluwarsa.tmp
disimpan: segar.tmpTiga berkas disemai dengan umur 3, 10, dan 40 hari relatif terhadap waktu beku, sehingga hanya kedaluwarsa.tmp yang melewati batas 30 hari. Di job nyata, ganti $kini dengan new DateTimeImmutable("now") — sisa logikanya tidak berubah sama sekali. Teknik membekukan waktu dan mtime ini juga membuat pembersih semacam ini dapat diuji sebagai unit test yang selalu hijau.
Pola Dry-Run #
Tool penghapus yang langsung bekerja adalah tool yang kelak menyesal. Pola dry-run memisahkan perencanaan dari eksekusi: kumpulkan daftar aksi lebih dulu, tampilkan bila flag --dry-run aktif, dan hanya jalankan tanpa flag tersebut:
<?php
// Simulasi CLI: php bersihkan.php --dry-run
$argv = ["bersihkan.php", "--dry-run"];
$dryRun = in_array("--dry-run", $argv, true);
$dir = "/tmp/fsauto/dry-run";
if (!is_dir($dir)) {
mkdir($dir, 0777, true);
}
foreach (glob("$dir/*") as $lama) {
unlink($lama);
}
foreach (["sesi-lama", "tmp-gagal", "debug"] as $nama) {
file_put_contents("$dir/$nama.log", "isi\n");
}
$rencana = [];
foreach (scandir($dir) as $nama) {
if (str_ends_with($nama, ".log")) {
$rencana[] = $nama;
}
}
if ($dryRun) {
echo "DRY RUN - " . count($rencana) . " berkas akan dihapus:\n";
foreach ($rencana as $nama) {
echo " (dry run) unlink $nama\n";
}
exit(0);
}
foreach ($rencana as $nama) {
unlink("$dir/$nama");
echo "dihapus: $nama\n";
}Output yang Dihasilkan:
DRY RUN - 3 berkas akan dihapus:
(dry run) unlink debug.log
(dry run) unlink sesi-lama.log
(dry run) unlink tmp-gagal.logDengan flag, tool hanya melaporkan apa yang akan dilakukan; tanpa flag, aksi yang sama benar-benar dijalankan:
php bersihkan.phpOutput yang Dihasilkan:
dihapus: debug.log
dihapus: sesi-lama.log
dihapus: tmp-gagal.logStruktur kodenya penting: pengumpulan $rencana dan eksekusinya terpisah, sehingga keduanya dijamin melihat daftar yang sama. Bila Anda menambah filter baru — misalnya batas umur dari bagian sebelumnya — cukup ubah tahap pengumpulan; mode dry-run dan eksekusi tetap sinkron otomatis.
Membuat Laporan Pemrosesan #
Job otomasi yang bisu membuat Anda harus memeriksa sendiri hasilnya setiap pagi. Penutup yang rapi dari setiap tool pengolah berkas adalah laporan ringkas — di sini berkas CSV yang bisa langsung dibuka spreadsheet atau diserap tool lain:
<?php
$dir = "/tmp/fsauto/laporan";
if (!is_dir($dir)) {
mkdir($dir, 0777, true);
}
foreach (glob("$dir/*") as $lama) {
unlink($lama);
}
foreach (["faktur-001.pdf", "faktur-002.pdf", "surat-jalan-007.pdf"] as $nama) {
file_put_contents("$dir/$nama", "isi\n");
}
$berkas = array_values(preg_grep("/\.pdf$/", scandir($dir)));
$fh = fopen("$dir/laporan.csv", "w");
fwrite($fh, "nama_berkas,status\n");
foreach ($berkas as $nama) {
fwrite($fh, $nama . ",diproses\n");
}
fclose($fh);
echo "Laporan: laporan.csv (" . count($berkas) . " berkas)\n";
echo file_get_contents("$dir/laporan.csv");Output yang Dihasilkan:
Laporan: laporan.csv (3 berkas)
nama_berkas,status
faktur-001.pdf,diproses
faktur-002.pdf,diproses
surat-jalan-007.pdf,diprosesLaporan ditulis dengan fwrite() baris demi baris dan ditutup dengan fclose() sebelum dibaca kembali — memastikan seluruh isi sudah tersimpan ke disk. Untuk data yang berisiko memuat koma atau tanda kutip, gunakan fputcsv() yang otomatis memberi tanda kutip sesuai aturan CSV. Laporan semacam ini cocok dilampirkan ke log job atau dikirim sebagai lampiran email oleh job terjadwal lain.
Ringkasan #
Empat pola tadi menutup mayoritas kebutuhan otomasi filesystem: normalisasi nama massal, rotasi arsip keep-last-N, pembersihan berdasarkan umur dengan filemtime(), semuanya diberi pagar arena uji yang dapat diulang dan mode dry-run. Laporan CSV menutup siklus dengan bukti tertulis atas apa yang sudah dilakukan. Tool-tool semacam ini makin lengkap bila bisa memanggil program lain — git, tar, atau ffmpeg — yang menjadi materi chapter Subprocesses berikutnya.