YAML #
YAML (YAML Ain’t Markup Language) adalah format serialisasi data yang dirancang untuk mudah dibaca dan ditulis manusia — berbeda dari JSON yang lebih cocok untuk mesin, YAML menggunakan indentasi whitespace dan sintaks minimal tanpa tanda kurung kurawal atau tanda kutip berlebihan. YAML adalah pilihan standar untuk file konfigurasi (Docker Compose, Kubernetes, GitHub Actions, Ansible semua menggunakan YAML), dan semakin sering digunakan sebagai alternatif .env untuk konfigurasi yang lebih terstruktur. PHP tidak memiliki parser YAML bawaan yang kaya — ada ekstensi yaml (PECL) dan library seperti symfony/yaml yang jauh lebih populer dan portabel. Artikel ini membahas sintaks YAML yang perlu dipahami, cara mem-parse dan menghasilkan YAML dari PHP, jebakan tipe data yang sering mengejutkan, dan pola konfigurasi aplikasi yang idiomatik.
Sintaks YAML — Yang Perlu Dipahami #
Sebelum parsing dari PHP, pahami sintaks YAML agar bisa membaca dan menulis file konfigurasi dengan benar.
Tipe Data Dasar #
# Komentar menggunakan tanda pagar
# String — tanda kutip opsional
nama: Budi Santoso
kota: "Jakarta" # kutip ganda boleh
provinsi: 'Jawa Barat' # kutip tunggal boleh
# Integer dan Float
umur: 28
tinggi: 172.5
suhu: -5
# Boolean — PERHATIAN: banyak nilai yang dianggap true/false!
aktif: true
nonaktif: false
ya: yes # true!
tidak: no # false!
hidup: on # true!
mati: off # false!
# Null
kosong: null
juga_kosong: ~ # tilde = null
# Tanggal — otomatis di-parse sebagai objek tanggal oleh beberapa parser
lahir: 1995-08-17
waktu: 2024-03-15T14:30:00+07:00
Struktur Data — Mapping dan Sequence #
# Mapping (seperti array asosiatif / object)
database:
host: localhost
port: 3306
nama: myapp_db
user: root
password: "rahasia123"
# Sequence (seperti array terindeks)
buah:
- apel
- mangga
- jeruk
# Sequence of mappings (array of objects)
produk:
- id: 1
nama: Laptop
harga: 15000000
- id: 2
nama: Monitor
harga: 5000000
# Flow style — inline (seperti JSON)
koordinat: {lat: -6.2088, lng: 106.8456}
tags: [php, web, backend]
# Nested — indentasi 2 spasi (konvensi, bukan kewajiban)
aplikasi:
server:
host: 0.0.0.0
port: 8080
workers: 4
database:
primary:
host: db-primary.example.com
port: 5432
replica:
host: db-replica.example.com
port: 5432
String Multi-baris #
# Literal block (|) — pertahankan newline
deskripsi: |
Baris pertama.
Baris kedua.
Baris ketiga dengan indent.
# Folded block (>) — newline diganti spasi (kecuali baris kosong)
ringkasan: >
Ini adalah paragraf panjang
yang dibungkus ke beberapa baris
tapi akan digabung jadi satu baris.
Baris baru dimulai setelah baris kosong.
# String yang mengandung karakter spesial — perlu dikutip
path: "C:\\Users\\Budi\\Documents"
regex: "^\\d{4}-\\d{2}-\\d{2}$"
tanda_petik: 'Dia berkata "halo"'
Anchors dan Aliases — Hindari Duplikasi #
# Anchor (&nama) mendefinisikan blok yang bisa digunakan kembali
# Alias (*nama) merujuk ke anchor
# Definisikan konfigurasi dasar
_base_db: &base_db
host: localhost
port: 5432
charset: utf8
# Gunakan di beberapa tempat dengan alias
development:
database:
<<: *base_db # merge anchor — salin semua key dari base_db
nama: myapp_dev
user: dev_user
production:
database:
<<: *base_db # salin lagi dari base_db
host: db.production.example.com # override host
nama: myapp_prod
user: prod_user
password: "password_kuat"
# Anchor untuk nilai skalar
_timeout: &timeout 30
api:
connect_timeout: *timeout
read_timeout: *timeout
write_timeout: *timeout
Instalasi Parser YAML #
PHP tidak memiliki dukungan YAML bawaan yang cukup. Ada dua pilihan utama:
# Opsi 1: symfony/yaml — murni PHP, tidak butuh ekstensi
# Paling direkomendasikan: portabel, aktif di-maintain, fitur lengkap
composer require symfony/yaml
# Opsi 2: ekstensi yaml (PECL) — lebih cepat, butuh instalasi sistem
sudo apt install php8.3-yaml
# atau via PECL:
pecl install yaml
| Aspek | symfony/yaml | Ekstensi yaml (PECL) |
|---|---|---|
| Instalasi | composer require | Kompilasi/apt |
| Portabilitas | Semua environment | Butuh instalasi sistem |
| Performa | Lebih lambat | Lebih cepat |
| Fitur | Lengkap + validasi | Standar |
| Maintenance | Aktif (Symfony) | Kurang aktif |
Gunakan symfony/yaml untuk hampir semua kasus — lebih mudah di-deploy dan lebih andal.
Parsing YAML dengan symfony/yaml #
<?php
use Symfony\Component\Yaml\Yaml;
use Symfony\Component\Yaml\Exception\ParseException;
// Parse string YAML
$yaml = <<<YAML
nama: Budi Santoso
umur: 28
aktif: true
alamat:
kota: Jakarta
kodePos: "10110"
hobi:
- membaca
- coding
- hiking
YAML;
$data = Yaml::parse($yaml);
echo $data['nama']; // Budi Santoso
echo $data['umur']; // 28
var_dump($data['aktif']); // bool(true)
echo $data['alamat']['kota']; // Jakarta
echo $data['hobi'][1]; // coding
// Parse dari file
$config = Yaml::parseFile('/path/to/config.yaml');
// Dengan penanganan error
try {
$config = Yaml::parseFile('config.yaml');
} catch (ParseException $e) {
throw new \RuntimeException(
"Gagal parse config.yaml: " . $e->getMessage(),
previous: $e
);
}
Flag Parsing Penting #
<?php
use Symfony\Component\Yaml\Yaml;
$yaml = "
aktif: true
nilai: 3.14
tanggal: 2024-03-15
kosong: ~
kode: '007'
";
// Default parsing
$data = Yaml::parse($yaml);
var_dump($data['aktif']); // bool(true)
var_dump($data['nilai']); // float(3.14)
var_dump($data['kode']); // string(3) "007"
// PARSE_DATETIME — parse tanggal menjadi \DateTime object
$data = Yaml::parse($yaml, Yaml::PARSE_DATETIME);
var_dump($data['tanggal']); // object(DateTime)
// PARSE_OBJECT_FOR_MAP — mapping menjadi stdClass bukan array
$data = Yaml::parse($yaml, Yaml::PARSE_OBJECT_FOR_MAP);
echo $data->aktif; // 1 (true)
echo $data->kode; // 007
// PARSE_CONSTANT — izinkan konstanta PHP dalam YAML
// Berguna untuk konfigurasi level PHP
$yamlDenganKonstanta = "level_log: !php/const PHP_INT_MAX";
$data = Yaml::parse($yamlDenganKonstanta, Yaml::PARSE_CONSTANT);
var_dump($data['level_log']); // int(9223372036854775807)
Dump PHP ke YAML #
<?php
use Symfony\Component\Yaml\Yaml;
$data = [
'aplikasi' => [
'nama' => 'Toko Online',
'versi' => '2.1.0',
'debug' => false,
],
'database' => [
'host' => 'localhost',
'port' => 3306,
'nama' => 'toko_db',
'user' => 'root',
'password' => 'rahasia',
],
'cache' => [
'driver' => 'redis',
'ttl' => 3600,
],
'fitur' => ['cart', 'wishlist', 'review'],
];
// Dump ke string YAML
$yaml = Yaml::dump($data);
echo $yaml;
/*
aplikasi:
nama: 'Toko Online'
versi: 2.1.0
debug: false
database:
host: localhost
port: 3306
...
*/
// Kontrol kedalaman inline — array pendek di-inline jika di kedalaman tertentu
$yaml = Yaml::dump($data, indent: 2, inline: 3);
// inline: 3 berarti struktur di kedalaman >= 3 di-inline sebagai flow style
// Simpan ke file
file_put_contents('config.yaml', Yaml::dump($data, 2, 2));
// Dump dengan flag
$yaml = Yaml::dump($data, 2, 2,
Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK // string panjang pakai block style |
| Yaml::DUMP_EMPTY_ARRAY_AS_SEQUENCE // [] bukan {}
);
Jebakan Tipe Data YAML #
YAML memiliki beberapa konversi tipe otomatis yang sering mengejutkan — terutama nilai yang terlihat seperti string tapi diinterpretasikan sebagai tipe lain:
# Jebakan 1: nilai yang terlihat boolean tapi bukan di YAML 1.2
# Di YAML 1.1 (yang banyak dipakai):
ya: yes # → true
tidak: no # → false
hidup: on # → true
mati: off # → false
y: y # → true (!) di beberapa parser
n: n # → false (!)
# Di YAML 1.2 (lebih ketat):
# hanya true/false yang dianggap boolean, yes/no/on/off adalah string
<?php
use Symfony\Component\Yaml\Yaml;
// Ini BERBEDA hasilnya tergantung versi YAML!
$yaml = "aktif: yes";
$data = Yaml::parse($yaml);
var_dump($data['aktif']); // bool(true) — bukan string "yes"!
// Solusi: kutip nilai yang memang string
$yaml = "aktif: 'yes'";
$data = Yaml::parse($yaml);
var_dump($data['aktif']); // string(3) "yes" — dengan kutip, tetap string
// Jebakan 2: angka oktal
// 0755 di YAML 1.1 diinterpretasikan sebagai oktal = 493 desimal!
$yaml = "permission: 0755";
$data = Yaml::parse($yaml);
var_dump($data['permission']); // int(493) bukan int(755)!
// Solusi: kutip sebagai string jika memang butuh string "0755"
$yaml = "permission: '0755'";
// Jebakan 3: nilai yang terlihat seperti float
$yaml = "versi: 1.0";
$data = Yaml::parse($yaml);
var_dump($data['versi']); // float(1.0) — bukan string "1.0"
// Jebakan 4: string yang terlihat seperti null
$yaml = "nama: ~";
$data = Yaml::parse($yaml);
var_dump($data['nama']); // NULL — tilde = null di YAML!
// Jebakan 5: tanggal ISO
$yaml = "lahir: 1995-08-17";
$data = Yaml::parse($yaml);
var_dump($data['lahir']); // string(10) "1995-08-17" di symfony/yaml default
// tapi bisa jadi DateTime di parser lain atau dengan flag PARSE_DATETIME
Selalu kutip nilai string yang ambigu — terutamayes,no,on,off,true,false, angka yang diawali nol, dan tanggal ISO 8601. Kutip tunggal ('nilai') paling aman karena tidak ada escape sequence di dalamnya.
YAML untuk Konfigurasi Aplikasi #
Pola paling umum: satu file YAML per environment, di-load saat aplikasi boot.
Struktur File Konfigurasi #
# config/app.yaml
app:
nama: "Toko Online Nusantara"
versi: "2.1.0"
url: "https://toko.example.com"
debug: false
timezone: "Asia/Jakarta"
locale: "id_ID"
server:
host: "0.0.0.0"
port: 8080
workers: 4
timeout: 30
database:
default: mysql
connections:
mysql:
host: "${DB_HOST}" # nilai dari environment variable
port: 3306
nama: "${DB_NAME}"
user: "${DB_USER}"
password: "${DB_PASSWORD}"
charset: utf8mb4
options:
strict: true
timezone: "+07:00"
cache:
default: redis
stores:
redis:
host: "${REDIS_HOST}"
port: 6379
db: 0
ttl: 3600
array:
driver: array
mail:
driver: smtp
host: "${MAIL_HOST}"
port: 587
encryption: tls
dari:
address: "[email protected]"
nama: "Toko Online"
logging:
level: info
channel: daily
path: "storage/logs/app.log"
max_files: 14
fitur:
pembayaran:
- transfer_bank
- kartu_kredit
- gopay
- ovo
pengiriman:
- jne
- pos
- sicepat
ConfigLoader — Kelas untuk Membaca Konfigurasi #
<?php
use Symfony\Component\Yaml\Yaml;
class ConfigLoader
{
private array $config = [];
public function __construct(private string $baseDir)
{
}
public function muat(string $environment = 'production'): static
{
// Muat konfigurasi dasar
$baseFile = $this->baseDir . '/app.yaml';
if (file_exists($baseFile)) {
$this->config = Yaml::parseFile($baseFile);
}
// Override dengan konfigurasi environment
$envFile = $this->baseDir . "/app.{$environment}.yaml";
if (file_exists($envFile)) {
$envConfig = Yaml::parseFile($envFile);
$this->config = $this->mergeRekursif($this->config, $envConfig);
}
// Interpolasi environment variables
$this->config = $this->interpolasiEnv($this->config);
return $this;
}
public function get(string $kunci, mixed $default = null): mixed
{
// Akses dengan dot notation: 'database.connections.mysql.host'
$bagian = explode('.', $kunci);
$nilai = $this->config;
foreach ($bagian as $segmen) {
if (!is_array($nilai) || !array_key_exists($segmen, $nilai)) {
return $default;
}
$nilai = $nilai[$segmen];
}
return $nilai;
}
public function getWajib(string $kunci): mixed
{
$nilai = $this->get($kunci);
if ($nilai === null) {
throw new \RuntimeException("Konfigurasi '$kunci' wajib ada tapi tidak ditemukan");
}
return $nilai;
}
private function mergeRekursif(array $dasar, array $override): array
{
foreach ($override as $kunci => $nilai) {
if (is_array($nilai) && isset($dasar[$kunci]) && is_array($dasar[$kunci])) {
$dasar[$kunci] = $this->mergeRekursif($dasar[$kunci], $nilai);
} else {
$dasar[$kunci] = $nilai;
}
}
return $dasar;
}
private function interpolasiEnv(mixed $nilai): mixed
{
if (is_string($nilai)) {
// Ganti ${VAR_NAME} dengan nilai environment variable
return preg_replace_callback(
'/\$\{([A-Z_][A-Z0-9_]*)\}/',
function(array $m): string {
$envVal = getenv($m[1]);
if ($envVal === false) {
throw new \RuntimeException(
"Environment variable '{$m[1]}' tidak di-set"
);
}
return $envVal;
},
$nilai
);
}
if (is_array($nilai)) {
return array_map(fn($v) => $this->interpolasiEnv($v), $nilai);
}
return $nilai;
}
}
// Penggunaan
$config = (new ConfigLoader(__DIR__ . '/config'))
->muat(getenv('APP_ENV') ?: 'production');
echo $config->get('app.nama'); // Toko Online Nusantara
echo $config->get('database.connections.mysql.host'); // nilai dari DB_HOST
echo $config->get('server.port', 8080); // 8080
$config->getWajib('app.secret_key'); // RuntimeException jika tidak ada
Multi-Dokumen YAML #
Satu file YAML bisa berisi beberapa dokumen, dipisahkan dengan ---:
# deployment.yaml
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
spec:
replicas: 3
template:
spec:
containers:
- name: myapp
image: myapp:latest
---
apiVersion: v1
kind: Service
metadata:
name: myapp-service
spec:
type: LoadBalancer
ports:
- port: 80
<?php
use Symfony\Component\Yaml\Yaml;
// Parse multi-dokumen YAML (hasilkan array of documents)
$konten = file_get_contents('deployment.yaml');
$dokumen = [];
// Split manual berdasarkan '---'
foreach (preg_split('/^---$/m', $konten, -1, PREG_SPLIT_NO_EMPTY) as $bagian) {
if (trim($bagian) !== '') {
$dokumen[] = Yaml::parse($bagian);
}
}
foreach ($dokumen as $i => $doc) {
echo "Dokumen $i: kind=" . $doc['kind'] . "\n";
}
// Dokumen 0: kind=Deployment
// Dokumen 1: kind=Service
YAML vs JSON vs INI — Kapan Mana #
flowchart TD
A{Format apa\nyang tepat?} --> B{Dibaca manusia\nsecara langsung?}
B -- Ya --> C{Struktur\nkompleks?}
B -- Tidak --> D[JSON\nBagus untuk API\ndan penyimpanan]
C -- Ya --> E[YAML\nKonfigurasi kompleks\ndengan komentar]
C -- Tidak --> F{Hanya\nkey=value?}
F -- Ya --> G[INI / .env\nSederhana dan universal]
F -- Tidak --> E
style E fill:#dcfce7
style D fill:#dbeafe
style G fill:#fef9c3| Aspek | YAML | JSON | INI / .env |
|---|---|---|---|
| Keterbacaan | ★★★★★ | ★★★☆☆ | ★★★★☆ |
| Komentar | ✓ | ✗ | ✓ |
| Tipe data | Otomatis | Eksplisit | Hanya string |
| Struktur nested | ✓ | ✓ | Terbatas |
| Validasi | Butuh schema | Butuh schema | N/A |
| Parser PHP bawaan | ✗ | ✓ | ✓ (parse_ini_file) |
| Cocok untuk | Konfigurasi, CI/CD | API, storage | Variabel env sederhana |
Gunakan YAML untuk:
✓ File konfigurasi aplikasi yang dibaca developer
✓ CI/CD pipeline (GitHub Actions, GitLab CI)
✓ Infrastructure as Code (Ansible, Kubernetes)
✓ Konfigurasi yang butuh komentar dan struktur hierarkis
Gunakan JSON untuk:
✓ Komunikasi API (request/response)
✓ Penyimpanan data terstruktur
✓ Package manifest (composer.json)
✓ Ketika kecepatan parsing penting
Gunakan INI/.env untuk:
✓ Variabel environment sensitif (password, API key)
✓ Konfigurasi minimal tanpa struktur nested
Anti-Pattern YAML yang Sering Ditemui #
<?php
use Symfony\Component\Yaml\Yaml;
// ✗ Anti-pattern 1: simpan nilai boolean/null sebagai string tanpa kutip
// Ini BUKAN string "true" — ini boolean true!
$yaml = "debug: true";
$data = Yaml::parse($yaml);
var_dump($data['debug']); // bool(true), bukan string "true"
// ✓ Jika memang butuh string: gunakan kutip
$yaml = "debug: 'true'";
// ✗ Anti-pattern 2: simpan password/secret langsung di YAML yang di-commit ke Git
# config/production.yaml — JANGAN LAKUKAN INI
# database:
# password: "SuperRahasiaBanget123!"
// ✓ Gunakan referensi environment variable
$yaml = "
database:
password: '\${DB_PASSWORD}'
";
// Nilai sebenarnya di .env yang tidak di-commit
// ✗ Anti-pattern 3: abaikan error parsing
$config = Yaml::parseFile('config.yaml');
// Jika file tidak ada atau YAML tidak valid, ParseException dilempar tanpa ditangkap
// ✓ Tangani error parsing dengan informatif
try {
$config = Yaml::parseFile('config.yaml');
} catch (\Symfony\Component\Yaml\Exception\ParseException $e) {
throw new \RuntimeException(
"Konfigurasi tidak valid di baris {$e->getParsedLine()}: {$e->getMessage()}"
);
} catch (\RuntimeException $e) {
throw new \RuntimeException("File konfigurasi tidak ditemukan: config.yaml");
}
// ✗ Anti-pattern 4: gunakan tab untuk indentasi
// YAML tidak mengizinkan tab sebagai indentasi — hanya spasi!
// Pastikan editor dikonfigurasi untuk tidak menyisipkan tab di file YAML
// ✗ Anti-pattern 5: regenerate YAML dari array PHP yang mengandung data user
// Data user bisa mengandung karakter yang merusak YAML
$data = ['komentar' => ": ini mengandung titik dua yang tidak dikutip"];
$yaml = Yaml::dump($data);
// symfony/yaml akan menangani ini dengan benar (menambahkan kutip otomatis)
// tapi tidak semua library sama — selalu gunakan library, jangan build YAML manual
Ringkasan #
symfony/yamladalah library YAML terbaik untuk PHP — murni PHP tanpa ekstensi tambahan, portabel, aktif di-maintain oleh tim Symfony. Instal dengancomposer require symfony/yaml.- Jebakan tipe data YAML:
yes/no/on/offadalah boolean, tilde (~) adalah null, angka berawalan nol adalah oktal. Selalu kutip nilai string yang ambigu dengan tanda kutip tunggal ('nilai').- Anchors (
&nama) dan aliases (*nama) mencegah duplikasi konfigurasi — definisikan sekali, gunakan berkali-kali dengan<<: *namauntuk merge.- Interpolasi environment variable — jangan simpan password di YAML yang di-commit ke Git. Gunakan placeholder
${VAR_NAME}dan interpolasikan dari environment saat runtime.- Dot notation untuk akses nested —
config.get('database.connections.mysql.host')jauh lebih bersih dari array access berantai.- Multi-dokumen dipisahkan dengan
---— berguna untuk file Kubernetes atau Ansible yang berisi beberapa resource dalam satu file.- YAML vs JSON vs INI — YAML untuk konfigurasi hierarkis yang dibaca manusia; JSON untuk API dan storage; INI/
.envuntuk variabel environment sederhana.- Jangan bangun YAML manual via string concatenation — selalu gunakan
Yaml::dump()agar escaping dan formatting ditangani dengan benar.