YAML

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
Aspeksymfony/yamlEkstensi yaml (PECL)
Instalasicomposer requireKompilasi/apt
PortabilitasSemua environmentButuh instalasi sistem
PerformaLebih lambatLebih cepat
FiturLengkap + validasiStandar
MaintenanceAktif (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 — terutama yes, 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
AspekYAMLJSONINI / .env
Keterbacaan★★★★★★★★☆☆★★★★☆
Komentar
Tipe dataOtomatisEksplisitHanya string
Struktur nestedTerbatas
ValidasiButuh schemaButuh schemaN/A
Parser PHP bawaan✓ (parse_ini_file)
Cocok untukKonfigurasi, CI/CDAPI, storageVariabel 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/yaml adalah library YAML terbaik untuk PHP — murni PHP tanpa ekstensi tambahan, portabel, aktif di-maintain oleh tim Symfony. Instal dengan composer require symfony/yaml.
  • Jebakan tipe data YAML: yes/no/on/off adalah 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 <<: *nama untuk 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 nestedconfig.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/.env untuk variabel environment sederhana.
  • Jangan bangun YAML manual via string concatenation — selalu gunakan Yaml::dump() agar escaping dan formatting ditangani dengan benar.

← Sebelumnya: JSON   Berikutnya: MySQL →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact