Hampir semua operasi berkas diawali oleh satu hal yang sama: sebuah path. Kesalahan path adalah sumber bug paling umum dalam aplikasi PHP — skrip tiba-tiba tidak menemukan konfigurasinya saat dijalankan lewat cron, folder kerja berpindah, atau kode yang mulus di Linux gagal di Windows. Pada Chapter ini kita akan membedah cara PHP merepresentasikan dan mengolah path: magic constant __DIR__/__FILE__, fungsi pengurai seperti dirname(), normalisasi dengan realpath(), perbedaan pemisah direktori antar sistem operasi, hingga pola penyimpanan berkas di luar document root.

Merujuk Berkas Relatif terhadap Skrip #

PHP menyediakan dua magic constant yang relevan dengan path: __FILE__ berisi path absolut berkas yang sedang dieksekusi, dan __DIR__ berisi direktori dari berkas tersebut — kepanjangan dari dirname(__FILE__). Dengan kedua konstanta ini, path dibangun relatif terhadap lokasi skrip, bukan terhadap direktori kerja (current working directory). Direktori kerja bisa berbeda tergantung dari mana skrip dijalankan — lewat terminal dari folder lain, lewat cron job, atau lewat web server — sedangkan lokasi skrip sendiri selalu tetap.

php
<?php
$configPath = __DIR__ . "/config/app.php";

echo "Nama berkas: " . basename($configPath) . "\n";
echo "Ekstensi: " . pathinfo($configPath, PATHINFO_EXTENSION) . "\n";
var_dump(str_ends_with(dirname($configPath), DIRECTORY_SEPARATOR . "config"));

Output yang Dihasilkan:

text
Nama berkas: app.php
Ekstensi: php
bool(true)

Kode sengaja tidak mencetak nilai __DIR__ secara mentah karena isinya adalah path absolut yang berbeda di setiap mesin. Yang dicetak hanyalah turunan yang pasti sama di mana pun skrip berada: nama berkasnya app.php, ekstensinya php, dan direktori induknya selalu berakhiran config. Pola __DIR__ . "/config/app.php" inilah yang dipakai hampir semua framework dan library modern untuk merujuk berkas internal proyek secara stabil.

Memotong Path dengan dirname() dan basename() #

dirname() mengembalikan bagian direktori dari sebuah path, sedangkan basename() mengembalikan segmen terakhirnya. Keduanya bekerja murni pada string sehingga path yang dipotong tidak perlu benar-benar ada di disk. Sejak PHP 7, dirname() menerima argumen kedua untuk naik beberapa tingkat direktori sekaligus.

php
<?php
$path = "/var/www/aplikasi/public/index.php";

echo dirname($path) . "\n";
echo dirname($path, 2) . "\n";
echo dirname($path, 3) . "\n";
echo basename($path) . "\n";
echo basename(dirname($path)) . "\n";

Output yang Dihasilkan:

text
/var/www/aplikasi/public
/var/www/aplikasi
/var/www
index.php
public

Perhatikan pola pada hasil di atas: dirname($path) naik satu tingkat ke public dihilangkan, dirname($path, 2) naik dua tingkat sekaligus, dan basename(dirname($path)) mengambil nama folder induk langsung dari sebuah berkas — trik yang sering dipakai untuk mendeteksi konteks tanpa membedah path manual. Kombinasi basename() juga menerima akhiran yang ingin dibuang: basename("laporan.pdf", ".pdf") menghasilkan laporan, berguna saat Anda perlu nama tanpa ekstensi.

Menormalkan Path dengan realpath() #

realpath() menerima path dalam bentuk apa pun — boleh relatif, boleh berantakan dengan segmen . dan .. — lalu mengembalikan path absolut kanoniknya: tanpa segmen redundan dan dengan symbolic link sudah diselesaikan. Jika path yang dimaksud tidak ada di filesystem, realpath() mengembalikan false, bukan string. Karena perilaku tersebut, hasil realpath() selalu perlu diperiksa sebelum dipakai lebih lanjut.

php
<?php
$base = sys_get_temp_dir() . "/kursus-paths-demo";
if (!is_dir($base . "/src")) {
    mkdir($base . "/src", 0777, true);
}
$berkas = $base . "/src/app.php";
file_put_contents($berkas, "<?php\n");

$berantakan = $base . "/./src/../src/app.php";
var_dump(realpath($berantakan) === realpath($berkas));
var_dump(realpath($base . "/tidak-ada.php"));

unlink($berkas);
rmdir($base . "/src");
rmdir($base);

Output yang Dihasilkan:

text
bool(true)
bool(false)

Path $berantakan mengandung segmen . dan .. yang merujuk ke berkas yang sama dengan $berkas. Fungsi realpath() menormalkan keduanya ke bentuk kanonik yang identik, sehingga perbandingan === menghasilkan true. Contoh ini sengaja membandingkan dua hasil realpath() alih-alih mencetaknya, karena path absolut hasil fungsi tersebut berbeda di setiap mesin. Untuk path yang tidak ada, hasilnya bool(false).

Pemisah Direktori Lintas Sistem Operasi #

Linux dan macOS memakai / sebagai pemisah direktori, sedangkan Windows secara bawaan memakai \. PHP menyediakan konstanta DIRECTORY_SEPARATOR yang berisi pemisah milik sistem operasi tempat PHP berjalan. Kabar baiknya: PHP selalu memahami / di semua sistem operasi, termasuk pada build Windows, sehingga menulis path dengan / adalah pilihan yang portabel.

php
<?php
echo "Pemisah direktori: '" . DIRECTORY_SEPARATOR . "'\n";

$path = "uploads/foto-profil.jpg";
echo "basename: " . basename($path) . "\n";
echo "dirname: " . dirname($path) . "\n";

Output yang Dihasilkan:

text
Pemisah direktori: '/'
basename: foto-profil.jpg
dirname: uploads

Arah sebaliknya tidak berlaku: di Linux, karakter \ dianggap karakter biasa dalam nama berkas, bukan pemisah direktori. Perhatikan contoh berikut yang memakai path bergaya Windows:

php
<?php
$path = "C:\\xampp\\htdocs\\index.php";
var_dump(basename($path));
var_dump(dirname($path));

Output yang Dihasilkan:

text
string(25) "C:\xampp\htdocs\index.php"
string(1) "."

Pada eksekusi di Linux, basename() menganggap seluruh string sebagai satu nama berkas karena tidak ada / di dalamnya, dan dirname() mengembalikan "." (direktori saat ini). Di Windows, hasilnya akan menjadi index.php dan C:\xampp\htdocs. Perilaku asimetris inilah alasan utama mengapa path dalam kode sebaiknya selalu ditulis dengan /.

Menormalkan Segmen .. Tanpa Berkas Nyata #

realpath() hanya bekerja untuk path yang benar-benar ada di disk. Kadang kita perlu membersihkan string path yang berasal dari input pengguna atau konfigurasi sebelum path tersebut dipakai — pada saat itu berkasnya mungkin belum dibuat. Untuk kebutuhan tersebut, normalisasi bisa dilakukan sebagai fungsi murni atas string:

php
<?php
function normalizePath(string $path): string
{
    $bagian = [];

    foreach (explode("/", $path) as $segmen) {
        if ($segmen === "" || $segmen === ".") {
            continue;
        }
        if ($segmen === "..") {
            if ($bagian !== []) {
                array_pop($bagian);
            }
            continue;
        }
        $bagian[] = $segmen;
    }

    return implode("/", $bagian);
}

echo normalizePath("uploads/../arsip/laporan.pdf") . "\n";
echo normalizePath("public/./css/../js/app.js") . "\n";
echo normalizePath("a/b/../../../c.txt") . "\n";

Output yang Dihasilkan:

text
arsip/laporan.pdf
public/js/app.js
c.txt

Mari kita telaah alur fungsinya: string dipecah per segmen dengan explode(), segmen kosong dan "." dibuang, segmen ".." membuang satu segmen dari tumpukan hasil, dan segmen lain ditambahkan ke tumpukan. Pada contoh ketiga, tiga buah ".." menembus batas akar path — fungsi ini memotongnya secara diam-diam, sedangkan realpath() akan mengembalikan false untuk kasus serupa. Perbedaan perilaku ini penting diingat saat Anda memilih fungsi mana yang dipakai untuk membersihkan input; validasi keamanan tetap wajib dibahas terpisah dan menjadi topik Chapter Filesystem Security.

Menyimpan Berkas di Luar Document Root #

Web server hanya menyajikan berkas yang berada di dalam document root — folder yang dipetakan ke akar URL situs. Berkas upload atau data privat yang disimpan di dalam document root dapat diakses langsung lewat URL, dan folder yang mengeksekusi PHP di dalamnya berubah menjadi lubang keamanan serius. Praktik yang benar adalah memisahkan folder penyimpanan ke luar document root, lalu menyajikan isinya melalui skrip PHP yang membaca berkas dengan readfile() dan mengatur header respons secara eksplisit.

php
<?php
// struktur proyek:
// /var/www/aplikasi/public    <- document root web server
// /var/www/aplikasi/storage   <- penyimpanan privat, di luar document root

define("PATH_STORAGE", dirname(__DIR__) . "/storage");

function pathUpload(string $namaAman): string
{
    return PATH_STORAGE . "/uploads/" . $namaAman;
}

Konstanta PATH_STORAGE dibangun dengan dirname(__DIR__) sehingga tetap benar meski proyek dipindah antar mesin — pola yang memanfaatkan seluruh teknik path pada Chapter ini. Strategi lengkap penamaan dan penataan folder penyimpanan upload akan kita rangkai pada Chapter Uploads dan File Storage.

Ringkasan #

Path yang benar dibangun dari tiga kebiasaan: merujuk berkas relatif terhadap skrip dengan __DIR__ alih-alih direktori kerja, memotong dan mengurai path dengan dirname()/basename()/pathinfo(), serta menormalkannya dengan realpath() atau fungsi normalisasi string ketika berkasnya belum ada. Menulis path dengan / membuat kode portabel antar sistem operasi, dan menyimpan berkas sensitif di luar document root menjaga berkas tersebut jauh dari jangkauan URL. Dengan fondasi path yang kokoh, kita siap membahas berkas yang memang sengaja hidup sesaat — temporary files — pada Chapter berikutnya.

Referensi #