Lingkungan PHP di dunia nyata jarang seragam. Server produksi sebuah perusahaan bisa masih PHP 8.1, shared hosting pelanggan tertinggal di 8.2, sementara mesin pengembang Anda menjalankan 8.4 — dan pustaka yang Anda tulis harus tetap benar di ketiganya. Chapter ini membahas perangkat yang dimiliki PHP untuk mengelola keragaman tersebut: pemeriksaan versi runtime, pola polyfill, deteksi fitur, matriks uji di CI, serta persyaratan platform di Composer. Tujuan akhirnya satu: kode gagal lebih dini dan lebih jelas ketika lingkungan tidak memenuhi syarat, alih-alih salah perilaku secara diam-diam.
Memeriksa Versi Runtime dengan PHP_VERSION_ID #
PHP menyediakan konstanta PHP_VERSION_ID berupa bilangan bulat yang mengodekan versi: mayor dikali 10.000, ditambah minor dikali 100, ditambah nomor rilis. PHP 8.4.23 misalnya menjadi 80423. Angka tunggal ini jauh lebih mudah dibandingkan daripada mengurai string PHP_VERSION, dan sangat cocok untuk gerbang fitur sederhana.
<?php
printf("PHP_VERSION_ID: %d\n", PHP_VERSION_ID);
if (PHP_VERSION_ID >= 80100) {
echo "Enums, readonly property, dan never tersedia.\n";
}
if (PHP_VERSION_ID < 80300) {
echo "json_validate() belum ada — siapkan polyfill.\n";
} else {
echo "json_validate() tersedia.\n";
}
echo "Versi: " . PHP_MAJOR_VERSION . "." . PHP_MINOR_VERSION . "." . PHP_RELEASE_VERSION . "\n";Output yang Dihasilkan:
PHP_VERSION_ID: 80423
Enums, readonly property, dan never tersedia.
json_validate() tersedia.
Versi: 8.4.23Mari kita bedah baris-barisnya:
- Baris 2 —
PHP_VERSION_IDdicetak apa adanya:80423untuk PHP 8.4.23. - Baris 4 — ambang
80100mewakili PHP 8.1.0; karena80423lebih besar, blok fitur 8.1 dianggap aman dipakai. - Baris 8 —
80300mewakili PHP 8.3.0; pada runtime 8.4 kondisi bernilai salah sehingga cabangelseyang berjalan. Pada PHP 8.2, cabangif-lah yang akan memberi peringatan dini. - Baris 14 — konstanta
PHP_MAJOR_VERSION,PHP_MINOR_VERSION, danPHP_RELEASE_VERSIONmenyediakan potongan versi secara terpisah untuk kebutuhan tampilan atau log.
Membandingkan Versi dengan version_compare #
PHP_VERSION_ID memadai untuk ambang sederhana, tetapi kebutuhan seperti rentang versi atau penanganan akhiran rc, beta, dan dev lebih pas ditangani version_compare(). Fungsi ini memahami semantik nomor versi, bukan sekadar urutan alfabetis.
<?php
var_dump(version_compare(PHP_VERSION, "8.1.0", ">="));
var_dump(version_compare("8.4.23", "8.5", "<"));
var_dump(version_compare("8.10", "8.9", ">"));
var_dump("8.10" > "8.9");Output yang Dihasilkan:
bool(true)
bool(true)
bool(true)
bool(false)Empat pembandingan tersebut memperlihatkan cara kerjanya:
- Baris 2 — runtime 8.4.23 dibandingkan dengan ambang 8.1.0; hasil
truekarena setiap segmen dibaca sebagai angka. - Baris 3 —
"8.4.23"dianggap lebih rendah dari"8.5"; versi 8.5 memang lebih tinggi dari 8.4 sekalipun teksnya lebih pendek. - Baris 4 —
version_compare("8.10", "8.9", ">")menghasilkantruekarena segmen10lebih besar dari9. - Baris 5 — perbandingan string biasa
"8.10" > "8.9"menghasilkanfalsekarena karakter1lebih kecil daripada9pada urutan byte — inilah jebakan yang harus dihindari.
Pola Polyfill dengan function_exists #
Polyfill adalah implementasi buatan sendiri (userland) untuk fungsi yang belum tersedia di versi PHP lama, dipasang hanya bila diperlukan. Polanya satu baris: bungkus definisi fungsi dengan function_exists() sehingga fungsi native tetap menang begitu runtime yang lebih baru menyediakannya. Pendekatan ini dipopulerkan paket seperti symfony/polyfill.
<?php
// Polyfill untuk PHP < 8.0: hanya dipasang bila fungsi belum ada
if (!function_exists("str_contains")) {
function str_contains(string $haystack, string $needle): bool
{
return $needle === "" || strpos($haystack, $needle) !== false;
}
}
var_dump(str_contains("Belajar PHP modern", "PHP"));Output yang Dihasilkan:
bool(true)Pada PHP 8.4, fungsi native str_contains sudah ada sehingga guard bernilai salah dan definisi fallback diabaikan — versi native yang melayani panggilan. Pada PHP 7.4, fungsi tersebut belum ada; guard memasang implementasi berbasis strpos sehingga kode pemanggil tetap berjalan tanpa perubahan.
Lalu bagaimana membuktikan cabang fallback benar-benar berfungsi tanpa PHP lama? Kita bisa meminjam perilaku resolusi nama dalam namespace: PHP selalu mencari fungsi di namespace saat ini lebih dulu sebelum jatuh ke fungsi global.
<?php
namespace Toko\Util;
// Pada PHP 8.4 fungsi global mb_trim sudah ada, tetapi di dalam
// namespace ini nama "Toko\Util\mb_trim" belum terdefinisi, sehingga
// guard polyfill mengambil cabang fallback — persis seperti di PHP 8.3.
if (!function_exists(__NAMESPACE__ . "\\mb_trim")) {
function mb_trim(string $teks): string
{
return preg_replace('/^\s+|\s+$/u', '', $teks);
}
}
var_dump(mb_trim(" Kopi Gayo "));Output yang Dihasilkan:
string(9) "Kopi Gayo"Apa yang terjadi pada berkas tersebut:
- Guard memeriksa nama lengkap —
function_exists(__NAMESPACE__ . "\\mb_trim")menanyakanToko\Util\mb_trim, yang memang belum terdefinisi, walau fungsi globalmb_trimtelah tersedia sejak PHP 8.4. - Fallback terpasang — definisi polyfill di dalam namespace dieksekusi, meniru perilaku fungsi global.
- Pemanggilan menembus versi lokal —
mb_trim(...)tanpa awalan\menemukan versi namespace lebih dulu, sehingga tubuh polyfill benar-benar berjalan dan hasilnya identik dengan versi native.
Deteksi Fitur Lebih Andal daripada Deteksi Versi #
Pemeriksaan nomor versi mengandung asumsi tersembunyi: fitur tertentu persis datang pada versi tertentu. Kenyataannya lebih berantakan — distribusi Linux kerap mem-backport perbaikan, build kustom bisa kehilangan ekstensi, dan begitu polyfill dimuat, fungsi tersedia terlepas dari versi. Deteksi fitur mengajukan pertanyaan yang sebenarnya: apakah yang saya butuhkan tersedia di lingkungan ini?
<?php
// Deteksi fitur, bukan deteksi versi
$fitur = [
"json_validate (PHP 8.3)" => function_exists("json_validate"),
"mb_trim (PHP 8.4)" => function_exists("mb_trim"),
"ekstensi mbstring" => extension_loaded("mbstring"),
];
foreach ($fitur as $nama => $ada) {
echo $nama . ": " . ($ada ? "ada" : "tidak ada") . "\n";
}Output yang Dihasilkan:
json_validate (PHP 8.3): ada
mb_trim (PHP 8.4): ada
ekstensi mbstring: adaTiga pemeriksaan pada contoh tersebut menggunakan dua fungsi yang berbeda:
function_exists()— bertanya langsung apakah sebuah fungsi bisa dipanggil; hasilnya otomatis benar baik fungsi berasal dari native maupun polyfill yang kebetulan sudah dimuat.extension_loaded()— memeriksa ketersediaan ekstensi;mb_trimbermakna hanya bilambstringaktif, sehingga pemeriksaan ini melengkapinya.- Pola gabungan — dictionary fitur memudahkan penambahan pemeriksaan baru dan pembacaan hasilnya dalam satu tampilan. Pemetaan fitur ke versi mana pun asalnya bisa Anda lacak di chapter Modern PHP Version Features .
Matriks Uji di CI #
Semua pemeriksaan di atas tidak berarti bila rangkaian uji Anda hanya pernah berjalan di satu versi. Integrasi berkelanjutan (CI) mengatasi hal ini dengan matriks: rangkaian uji yang sama diulang pada beberapa versi PHP, sehingga kode yang tidak sengaja memakai fitur 8.4 akan gagal pada job PHP 8.2 sebelum menyentuh produksi.
jobs:
uji:
strategy:
matrix:
php: ["8.1", "8.2", "8.3", "8.4"]
steps:
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
- run: composer install
- run: vendor/bin/phpunitKonfigurasi di atas adalah pola GitHub Actions: kunci strategy.matrix.php menduplikasi seluruh job untuk setiap nilai versi, action setup-php memasang versi yang diminta, lalu PHPUnit berjalan identik di setiap salinan. Kegagalan ditandai per versi sehingga Anda langsung tahu batas bawah kompatibilitas yang sebenarnya. Praktik pengujian itu sendiri dibahas tuntas pada Section Testing dan Quality Assurance
.
Persyaratan Platform di Composer #
Untuk pustaka yang dibagikan, cara paling awal menolak lingkungan yang tidak cocok adalah lewat composer.json: kunci require.php dan ext-* diperiksa Composer setiap kali dependensi dipasang atau diperbarui.
{
"name": "toko/util",
"require": {
"php": ">=8.2",
"ext-mbstring": "*"
}
}Deklarasi tersebut menyatakan dua hal: pustaka membutuhkan PHP 8.2 ke atas, dan ekstensi mbstring versi berapa pun. Perintah composer check-platform-reqs membandingkan lingkungan saat ini dengan seluruh persyaratan yang terpasang — berguna sebagai pemeriksaan terakhir sebelum deploy. Ketika tim Anda sengaja mengunci versi target agar berkas composer.lock deterministik, konfigurasi platform.php membuat solver dependensi berpura-pura berjalan pada versi tersebut.
composer check-platform-reqs
composer config platform.php 8.2.99Pengelolaan dependensi secara keseluruhan — versi semantik, constraint, dan lock file — dibahas tuntas pada Section Composer dan Dependency Management .
Merujuk Panduan Upgrade Resmi #
Setiap rilis mayor menerbitkan panduan migrasi resmi yang memuat tiga bagian penting: fitur baru, hal yang ditinggalkan (deprecated), dan perubahan yang merusak kompatibilitas mundur. Biasakan membaca bagian kedua dan ketiga sebelum upgrade — kebanyakan insiden upgrade berakar pada perubahan perilaku kecil yang tidak terbaca, bukan pada fitur baru. Status dukungan tiap versi (aktif, hanya keamanan, atau berakhir) dapat dipantau di halaman versi yang didukung pada situs php.net agar rencana upgrade Anda berpijak pada jadwal resmi.
Referensi #
- version_compare — php.net
- Panduan migrasi PHP 8.4 — php.net
— ganti
migration84menjadimigration83,migration82,migration81, ataumigration80untuk versi lain. - Versi PHP yang didukung — php.net
- Konfigurasi platform — getcomposer.org
Ringkasan #
PHP_VERSION_ID dan version_compare() menjadi gerbang versi di dalam kode; pola polyfill menjembatani fungsi yang belum lahir di runtime lama; deteksi fitur bertanya langsung pada lingkungan; matriks CI membuktikan klaim kompatibilitas; dan persyaratan platform di Composer menolak instalasi yang tidak memenuhi syarat sejak awal. Kombinasi kelimanya membuat dukungan multi-versi menjadi keputusan yang sadar dan teruji, bukan tebakan. Section berikutnya berbelok ke arah yang tampak berlawanan dari kompatibilitas yang lebar: memperketat kontrak kode melalui type system dan static analysis.