Aplikasi PHP yang sama hampir selalu berjalan di lebih dari satu tempat: di laptop Anda saat development, di server staging untuk pengujian, dan di server produksi untuk pengguna sungguhan. Meskipun kodenya identik, ketiga lingkungan tersebut membutuhkan konfigurasi yang berbeda — alamat database, kredensial, hingga mode tampilan error. Menulis konfigurasi semacam itu langsung di dalam kode membuat setiap perpindahan lingkungan menuntut perubahan kode dan membuka peluang kebocoran data produksi. Environment variables menyelesaikan masalah ini dengan menyimpan konfigurasi di lingkungan tempat proses berjalan, bukan di dalam berkas kode.

Mengapa Konfigurasi Dipisah dari Kode #

Prinsip twelve-factor app menyebutnya sebagai pemisahan config dari code: kode yang sama dirilis ke semua lingkungan, sedangkan hal yang berbeda antar lingkungan disuntikkan lewat environment. Dengan pola ini, memindahkan aplikasi dari staging ke produksi cukup mengganti nilai environment — tanpa menyentuh satu baris kode pun, dan tanpa membangun ulang paket aplikasi. Konfigurasi menjadi sesuatu yang dimiliki operator server, bukan sesuatu yang ditanam permanen di repositori.

Berikut contoh nilai yang lazim berbeda antar lingkungan:

Nama variableNilai developmentNilai production
APP_ENVdevelopmentproduction
APP_DEBUGtruefalse
DB_HOST127.0.0.110.0.1.20
DB_NAMEtoko_devtoko_prod
MAIL_MAILERlogsmtp

Sebagai patokan, semua nilai yang berpotensi berbeda antar lingkungan — alamat layanan, kredensial, flag debug — adalah kandidat kuat environment variable. Sebaliknya, konstanta bisnis murni seperti tarif pajak atau batas maksimal keranjang belanja lebih tepat hidup di dalam kode karena nilainya sama di lingkungan mana pun.

Membaca Nilai dengan getenv() dan putenv() #

PHP menyediakan function getenv() untuk membaca environment variable dan putenv() untuk menulisnya pada proses yang sedang berjalan. Function getenv() mengembalikan nilai bertipe string, atau false ketika variable yang diminta tidak ada. Mari kita uji keduanya secara langsung:

php
<?php
putenv('KURSUS_APP_ENV=development');

var_dump(getenv('KURSUS_APP_ENV'));
var_dump(getenv('KURSUS_TIDAK_ADA'));

Output yang Dihasilkan:

text
string(11) "development"
bool(false)

Mari kita telaah kode di atas:

  1. Baris 2 (putenv('KURSUS_APP_ENV=development');): menetapkan variable KURSUS_APP_ENV bernilai "development" pada environment proses PHP saat ini. Nama variable sengaja diberi prefix KURSUS_ agar mudah dibedakan dari environment sistem.
  2. Baris 4 (var_dump(getenv('KURSUS_APP_ENV'));): membaca kembali nilai yang baru ditetapkan dan mencetak tipenya — string(11) berarti string sepanjang 11 karakter.
  3. Baris 5 (var_dump(getenv('KURSUS_TIDAK_ADA'));): variable yang tidak pernah ditetapkan menghasilkan bool(false), bukan null dan bukan string kosong.

Perlu dicatat bahwa putenv() hanya berlaku untuk proses PHP saat ini beserta proses anak yang dibangkitkannya — perintah ini tidak mengubah environment shell tempat Anda mengetik. Di server sungguhan, nilai environment biasanya sudah diset oleh systemd, container runtime, atau supervisor sebelum PHP berjalan; putenv() paling sering kita pakai justru saat menguji kode konfigurasi seperti pada chapter ini.

variables_order dan Superglobal $_ENV #

Selain getenv(), PHP juga menyediakan superglobal $_ENV berupa array yang berisi snapshot environment saat proses PHP dimulai. Apakah array tersebut terisi ditentukan oleh direktif variables_order pada php.ini: nilainya adalah gabungan huruf E (Environment), G (Get), P (Post), C (Cookie), dan S (Server). Build PHP yang dipakai course ini berjalan tanpa berkas php.ini, sehingga memakai nilai bawaan EGPCS — huruf E ikut serta. Berbeda dengan itu, php.ini-development dan php.ini-production resmi dari PHP memilih GPCS, yang membuat $_ENV dibiarkan kosong.

php
<?php
$urutan = ini_get('variables_order');
echo "variables_order: $urutan\n";
echo "Huruf E ada: " . (str_contains($urutan, 'E') ? 'ya' : 'tidak') . "\n";

putenv('KURSUS_DB_HOST=127.0.0.1');
echo 'getenv: ' . getenv('KURSUS_DB_HOST') . "\n";
var_dump(array_key_exists('KURSUS_DB_HOST', $_ENV));

Output yang Dihasilkan:

text
variables_order: EGPCS
Huruf E ada: ya
getenv: 127.0.0.1
bool(false)

Tiga hal penting dapat kita amati dari kode di atas:

  1. Baris 2-4: ini_get() membaca nilai direktif variables_order dari konfigurasi build ini, yaitu EGPCS, sehingga huruf E terdeteksi.
  2. Baris 6-7: getenv() berhasil membaca nilai yang baru saja ditulis putenv() — getenv() selalu membaca environment asli proses tanpa peduli variables_order.
  3. Baris 8: meskipun $_ENV aktif karena E ada pada variables_order, array_key_exists() mengembalikan false — $_ENV adalah snapshot awal, sehingga penulisan lewat putenv() setelah proses berjalan tidak terpantau di dalamnya.

Semua Nilai Berbentuk String #

Environment variable tidak mengenal tipe data: angka, boolean, apa pun isinya dikirim sebagai teks. PHP tidak melakukan konversi otomatis saat Anda membacanya, sehingga getenv('KURSUS_MAX_RETRIES') yang berisi 3 tetap berupa string "3", bukan integer. Konsekuensinya, kita perlu melakukan casting secara eksplisit:

php
<?php
putenv('KURSUS_MAX_RETRIES=3');

$maxRetries = getenv('KURSUS_MAX_RETRIES');
var_dump($maxRetries);

$sebagaiInt = (int) $maxRetries;
var_dump($sebagaiInt + 1);

putenv('KURSUS_FEATURE_FLAG=true');
$fiturAktif = filter_var(getenv('KURSUS_FEATURE_FLAG'), FILTER_VALIDATE_BOOL);
var_dump($fiturAktif);

Output yang Dihasilkan:

text
string(1) "3"
int(4)
bool(true)

Mari kita bedah baris per baris:

  1. Baris 4-5: var_dump() menunjukkan string(1) "3" — nilai dari environment selalu string meskipun terlihat seperti angka.
  2. Baris 7-8: cast (int) mengubah string "3" menjadi integer 3, sehingga operasi aritmetika $sebagaiInt + 1 menghasilkan int(4).
  3. Baris 10-12: untuk boolean, filter_var() dengan FILTER_VALIDATE_BOOL mengenali string "1", "true", "on", dan "yes" sebagai true, serta nilai lainnya sebagai false — jauh lebih aman daripada casting (bool) yang menganggap semua string non-kosong sebagai true.

Pola Nilai Default yang Aman #

Variable opsional biasanya perlu nilai cadangan ketika tidak diset di environment. Karena getenv() mengembalikan false untuk variable yang tidak ada, pemeriksaan yang tepat adalah membandingkan hasilnya dengan false secara eksplisit:

php
<?php
function ambilEnv(string $kunci, ?string $default = null): ?string
{
    $nilai = getenv($kunci);
    return $nilai === false ? $default : $nilai;
}

putenv('KURSUS_APP_NAME=Toko Budi');
putenv('KURSUS_TIMEOUT=0');

echo ambilEnv('KURSUS_APP_NAME', 'Aplikasi Default') . "\n";
echo ambilEnv('KURSUS_TIDAK_ADA', 'Aplikasi Default') . "\n";

echo 'Dengan ?:: ' . (getenv('KURSUS_TIMEOUT') ?: '30') . "\n";
echo 'Dengan fungsi: ' . ambilEnv('KURSUS_TIMEOUT', '30') . "\n";

Output yang Dihasilkan:

text
Toko Budi
Aplikasi Default
Dengan ?:: 30
Dengan fungsi: 0

Mari kita amati hasilnya:

  1. Baris 2-6 (function ambilEnv): membaca variable; jika getenv() mengembalikan false, nilai $default yang dikembalikan — perbandingan memakai === false agar hanya variable yang benar-benar tidak ada yang digantikan default.
  2. Baris 11-12: variable KURSUS_APP_NAME ada sehingga nilainya tercetak, sedangkan KURSUS_TIDAK_ADA digantikan "Aplikasi Default".
  3. Baris 14-15: inilah jebakannya — KURSUS_TIMEOUT berisi string "0" yang sah, tetapi operator ?: menganggapnya falsy sehingga mencetak 30 (salah), sementara ambilEnv() dengan benar mencetak 0.

Berkas .env untuk Development #

Mengetik export KURSUS_DB_HOST=... setiap kali membuka terminal tentu melelahkan. Praktik umum di development adalah menyimpan daftar KEY=VALUE pada berkas .env di root proyek, lalu memuatnya ke environment saat aplikasi start. Pustaka seperti vlucas/phpdotenv dan Symfony Dotenv (dipakai Laravel dan Symfony) melakukannya dengan matang — menangani tanda kutip, export, dan variable bersarang. Berikut versi sederhananya agar kita paham mekanismenya:

php
<?php
function muatDotEnv(string $isiBerkas): void
{
    foreach (explode("\n", $isiBerkas) as $baris) {
        $baris = trim($baris);
        if ($baris === '' || str_starts_with($baris, '#') || !str_contains($baris, '=')) {
            continue;
        }
        [$kunci, $nilai] = explode('=', $baris, 2);
        putenv(trim($kunci) . '=' . trim($nilai));
    }
}

$isiEnv = <<<'ENV'
#-- Konfigurasi lingkungan development
KURSUS_APP_NAME=Toko Budi
KURSUS_DB_HOST=127.0.0.1
KURSUS_DB_NAME=toko_dev
ENV;

muatDotEnv($isiEnv);

echo getenv('KURSUS_APP_NAME') . "\n";
echo getenv('KURSUS_DB_HOST') . "\n";
echo getenv('KURSUS_DB_NAME') . "\n";

Output yang Dihasilkan:

text
Toko Budi
127.0.0.1
toko_dev

Function muatDotEnv() memecah isi berkas per baris, melewati baris kosong dan baris komentar berawalan #, lalu memisahkan kunci dan nilai pada tanda = pertama (batas 2 membuat nilai yang mengandung = tetap utuh). Setiap pasangan lalu didaftarkan lewat putenv(), dan hasilnya dapat dibaca getenv() seperti biasa. Format berkas konfigurasi seperti ini dibahas lebih mendalam pada chapter Configuration Files .

Mencantumkan Environment dengan Aman #

Saat men-debug konfigurasi, kita kadang ingin melihat variable mana saja milik aplikasi yang sudah termuat. Memanggil getenv() tanpa argumen mengembalikan seluruh environment sebagai array — mencetak semuanya adalah keputusan buruk karena environment juga memuat PATH, token CI, dan berbagai rahasia sistem. Cara yang aman adalah memfilter hanya prefix milik aplikasi kita sendiri:

php
<?php
putenv('KURSUS_DEMO_APP_NAME=Toko Budi');
putenv('KURSUS_DEMO_DB_HOST=127.0.0.1');
putenv('KURSUS_DEMO_DB_USER=budi');

foreach (getenv() as $kunci => $nilai) {
    if (str_starts_with($kunci, 'KURSUS_DEMO_')) {
        printf("%s=%s\n", $kunci, $nilai);
    }
}

Output yang Dihasilkan:

text
KURSUS_DEMO_APP_NAME=Toko Budi
KURSUS_DEMO_DB_HOST=127.0.0.1
KURSUS_DEMO_DB_USER=budi

Loop di atas hanya mencetak tiga variable yang diawali KURSUS_DEMO_ — sisanya diabaikan meskipun ada ratusan variable sistem di environment. Pola ini aman dipakai di halaman diagnostics internal karena output-nya terprediksi dan tidak membocorkan nilai di luar prefix aplikasi.

Ringkasan #

Environment variables memisahkan konfigurasi dari kode: nilai dibaca lewat getenv() yang selalu andal, semua nilai berbentuk string sehingga perlu cast eksplisit, nilai default sebaiknya diberikan lewat perbandingan === false, dan berkas .env mempermudah development dengan tetap tidak boleh ter-commit. Dengan pola ini, satu paket kode dapat berpindah lingkungan hanya dengan mengganti environment. Namun, sebagian nilai yang lewat jalur ini — password database, API key, kunci penandatanganan — menuntut perlakuan khusus karena kebocorannya berakibat fatal; itulah fokus chapter Managing Secrets berikutnya.

Referensi #