Ketika kita menambahkan dependensi di composer.json, kita hampir tidak pernah menyebut satu nomor versi tunggal. Alasannya praktis: dependensi terus menerima perbaikan keamanan dan perbaikan bug, dan mengunci satu angka membuat proyek kita kehilangan semua pembaruan itu. Sebagai gantinya, kita menulis version constraint — kendala yang menggambarkan rentang versi yang boleh dipakai — lalu biarkan Composer memilih satu versi terbaik di dalam rentang tersebut. Pada chapter ini kita bedah semua bentuk constraint, membuktikan artinya lewat sesi terminal nyata, dan belajar kapan memilih ^ dibandingkan ~.

Constraint Menentukan Rentang, Bukan Satu Versi #

Composer membaca nomor versi dalam gaya semantic versioning: MAJOR.MINOR.PATCH — misalnya 3.0.2. Naiknya PATCH berarti perbaikan bug yang kompatibel, naiknya MINOR berarti fitur baru yang kompatibel, sedangkan naiknya MAJOR berarti boleh ada perubahan yang merusak kode lama. Constraint kemudian kita tulis sebagai ekspresi atas tiga angka itu, dan setiap kali resolusi dijalankan, Composer selalu memilih versi tertinggi yang masuk rentang dan berstatus stabil.

Bentuk paling sederhana untuk melihat perilaku ini adalah membiarkan Composer memilih sendiri. Perintah composer require tanpa argumen versi akan memasang versi stabil terbaru, lalu mengubah kendala menjadi caret secara otomatis:

bash
cd demo-default
composer require psr/log

Output yang Dihasilkan:

text
./composer.json has been updated
Running composer update psr/log
Loading composer repositories with package information
Updating dependencies
Lock file operations: 1 install, 0 updates, 0 removals
  - Locking psr/log (3.0.2)
Writing lock file
Installing dependencies from lock file (including require-dev)
Package operations: 1 install, 0 updates, 0 removals
  - Installing psr/log (3.0.2): Extracting archive
Generating autoload files
No security vulnerability advisories found.
Using version ^3.0 for psr/log

Perhatikan baris terakhir: Using version ^3.0 for psr/log. Berkas composer.json pun kini memuat "psr/log": "^3.0" — caret adalah pilihan bawaan Composer karena dianggap paling aman untuk dependensi yang mengikuti semantic versioning. Fakta bahwa versi 3.0.2 terpasang menunjukkan aturan dasarnya: dari semua versi yang diizinkan kendala, versi tertingginya yang menang.

Versi Eksak dan Wildcard #

Bentuk paling tegas adalah versi eksak: tulis nomor versi lengkap, dan Composer wajib memasangnya persis. Kita siapkan proyek kecil bernama demo-kendala dengan composer.json minimal, lalu jalankan composer require dengan versi eksak 1.1.4 milik paket psr/log:

bash
cd demo-kendala
composer require psr/log:"1.1.4"

Output yang Dihasilkan:

text
./composer.json has been updated
Running composer update psr/log
Loading composer repositories with package information
Updating dependencies
Lock file operations: 1 install, 0 updates, 0 removals
  - Locking psr/log (1.1.4)
Writing lock file
Installing dependencies from lock file (including require-dev)
Package operations: 1 install, 0 updates, 0 removals
  - Downloading psr/log (1.1.4)
  - Installing psr/log (1.1.4): Extracting archive
Generating autoload files
No security vulnerability advisories found.

Baris - Locking psr/log (1.1.4) dan - Installing psr/log (1.1.4) menegaskan bahwa hanya versi itu yang boleh dipakai. Versi eksak cocok untuk kasus khusus seperti mereproduksi environment lama, tetapi tidak disarankan untuk require sehari-hari karena menolak semua perbaikan berikutnya.

Wildcard menggantikan salah satu segmen dengan * atau x: 1.1.* berarti “versi 1.1 berapa pun patch-nya”, dan 1.* berarti “versi 1 berapa pun minor-nya”. Operator perbandingan >=, >, <, <=, dan != juga sah, bahkan digabung dengan spasi untuk membentuk rentang eksplisit seperti >=1.0 <2.0. Mari kita ganti kendala di proyek yang sama dan amati reaksinya:

bash
composer require psr/log:"1.1.*"

Output yang Dihasilkan:

text
./composer.json has been updated
Running composer update psr/log
Loading composer repositories with package information
Updating dependencies
Nothing to modify in lock file
Writing lock file
Installing dependencies from lock file (including require-dev)
Nothing to install, update or remove
Generating autoload files
No security vulnerability advisories found.

Hasilnya Nothing to modify in lock file — versi 1.1.4 yang sudah terpasang memang angka tertinggi dalam rentang 1.1.*, sehingga tidak ada yang perlu diubah. Dengan kendala ^1.0 hasilnya sama persis: rentangnya lebih lebar (seluruh versi 1.x), tetapi pemenangnya tetap 1.1.4.

Caret dan Tilde #

Dua operator paling penting di ekosistem PHP adalah ^ (caret) dan ~ (tilde). Keduanya sama-sama mengizinkan perubahan kompatibel dan menolak lompatan yang berisiko, tetapi definisi “kompatibel” mereka berbeda — dan perbedaan itu baru terasa ketika jumlah komponennya berbeda.

Caret ^X.Y.Z mengizinkan semua kenaikan sampai — tetapi tidak termasuk — rilis major berikutnya. Untuk ^1.0 artinya >=1.0.0 <2.0.0, dan untuk ^2.0 artinya >=2.0.0 <3.0.0. Mari kita buktikan dua-duanya di terminal:

bash
composer require psr/log:"^2.0"

Output yang Dihasilkan:

text
./composer.json has been updated
Running composer update psr/log
Loading composer repositories with package information
Updating dependencies
Lock file operations: 0 installs, 1 update, 0 removals
  - Upgrading psr/log (1.1.0 => 2.0.0)
Writing lock file
Installing dependencies from lock file (including require-dev)
Package operations: 0 installs, 1 update, 0 removals
  - Upgrading psr/log (1.1.0 => 2.0.0): Extracting archive
Generating autoload files
No security vulnerability advisories found.

Kendala ^2.0 membuat Composer menaikkan paket dari 1.1.0 ke 2.0.0 — versi 2.x tertinggi yang tersedia dari paket ini memang 2.0.0, dan 3.0.2 tidak boleh diambil karena melewati batas major. Aturan caret punya satu pengecualian penting untuk versi 0.x: karena 0 menandakan API yang belum stabil, caret menjadi konservatif — ^0.4.2 berarti >=0.4.2 <0.5.0, bukan sampai 1.0.0.

Tilde ~ bergantung pada jumlah komponen yang ditulis. Bentuk dua komponen ~1.0 berarti >=1.0.0 <2.0.0 — sama dengan caret. Bentuk tiga komponen ~1.0.0 berarti >=1.0.0 <1.1.0 — hanya patch yang boleh naik. Perbedaan ini menghasilkan efek nyata di terminal kita:

bash
composer require psr/log:"~1.0.0"

Output yang Dihasilkan:

text
./composer.json has been updated
Running composer update psr/log
Loading composer repositories with package information
Updating dependencies
Lock file operations: 0 installs, 1 update, 0 removals
  - Downgrading psr/log (1.1.4 => 1.0.2)
Writing lock file
Installing dependencies from lock file (including require-dev)
Package operations: 0 installs, 1 update, 0 removals
  - Downloading psr/log (1.0.2)
  - Downgrading psr/log (1.1.4 => 1.0.2): Extracting archive
Generating autoload files
No security vulnerability advisories found.

Composer justru menurunkan versi dari 1.1.4 ke 1.0.2, karena ~1.0.0 menutup pintu di depan 1.1.0. Ketika kita menjalankan composer require psr/log:"~1.0" (dua komponen), Composer menaikkannya kembali ke 1.1.4 — bukti bahwa menambah atau membuang satu angka mengubah arti tilde secara drastis.

Rentang Hyphen #

Notasi hyphen A - B menyatakan rentang inklusif: kedua ujung ikut diizinkan. Kendala 1.0.0 - 1.1.0 berarti >=1.0.0 <=1.1.0, dan ujung boleh ditulis parsial — 1.0 - 2.0 setara dengan >=1.0.0 <=2.0.0. Hasil nyatanya di sesi kita:

bash
composer require psr/log:"1.0.0 - 1.1.0"

Output yang Dihasilkan:

text
./composer.json has been updated
Running composer update psr/log
Loading composer repositories with package information
Updating dependencies
Lock file operations: 0 installs, 1 update, 0 removals
  - Downgrading psr/log (1.1.4 => 1.1.0)
Writing lock file
Installing dependencies from lock file (including require-dev)
Package operations: 0 installs, 1 update, 0 removals
  - Downloading psr/log (1.1.0)
  - Downgrading psr/log (1.1.4 => 1.1.0): Extracting archive
Generating autoload files
No security vulnerability advisories found.

Versi 1.1.0 terpasang karena batas atas rentang ikut dihitung — persis perilaku inklusif. Tabel berikut merangkum seluruh eksperimen kita dengan paket psr/log, lengkap dengan versi yang benar-benar terpasang di setiap kendala:

KendalaRentang setaraVersi terpasang
1.1.4hanya 1.1.41.1.4
1.1.*>=1.1.0 <1.2.01.1.4
^1.0>=1.0.0 <2.0.01.1.4
~1.0>=1.0.0 <2.0.01.1.4
~1.0.0>=1.0.0 <1.1.01.0.2
1.0.0 - 1.1.0>=1.0.0 <=1.1.01.1.0
1.0 - 2.0>=1.0.0 <=2.0.02.0.0
^2.0>=2.0.0 <3.0.02.0.0
>=1.0 <2.0rentang eksplisit1.1.4
*semua versi stabil3.0.2

Stabilitas Versi #

Tidak semua rilis berstatus stabil. Versi prarilis memakai akhiran seperti 3.6.0-BETA1 atau 3.6.0-RC1, dan cabang git ditulis dengan prefiks dev-. Kita bisa melihat seluruh versi sebuah paket — termasuk yang tidak stabil — dengan composer show -a:

bash
composer show -a psr/log

Output yang Dihasilkan:

text
versions : dev-master, 3.x-dev, 3.0.2, 3.0.1, 3.0.0, 2.0.0, 1.1.4, 1.1.3, 1.1.2, 1.1.1, 1.1.0, 1.0.2, 1.0.1, 1.0.0, dev-add-funding

Entri dev-master dan 3.x-dev adalah branch yang masih bergerak, bukan rilis yang dibekukan — memakainya berarti mengunci pada satu commit tertentu, dan itu tercermin di composer.lock sebagai nama cabang plus hash commit. Urutan stabilitas dari paling rendah ke paling tinggi adalah dev, alpha, beta, RC, lalu stable; secara bawaan Composer hanya menerima stable.

Ada dua cara menulis kendala yang menyentuh versi tidak stabil. Pertama, sufiks pada kendala — misalnya 1.2.3@beta atau dev-main@dev — yang menaikkan ambang stabilitas khusus untuk kendala itu. Kedua, tulis versinya secara eksplisit; ketika root project menyebut versi prarilis apa adanya, Composer otomatis memberi stability flag pada kendala tersebut. Kita buktikan dengan versi beta dari symfony/deprecation-contracts:

bash
cd demo-stabilitas
composer update

Output yang Dihasilkan:

text
Loading composer repositories with package information
Updating dependencies
Lock file operations: 1 install, 0 updates, 0 removals
  - Locking symfony/deprecation-contracts (v3.6.0-BETA1)
Writing lock file
Installing dependencies from lock file (including require-dev)
Package operations: 1 install, 0 updates, 0 removals
  - Downloading symfony/deprecation-contracts (v3.6.0-BETA1)
  - Installing symfony/deprecation-contracts (v3.6.0-BETA1): Extracting archive
Generating autoload files
1 package you are using is looking for funding.
Use the `composer fund` command to find out more!
No security vulnerability advisories found.

Versi v3.6.0-BETA1 terpasang mulus karena kendalanya kita tulis eksplisit di composer.json. Cerita berbeda terjadi pada dependensi transisif: ketika paket lain yang membutuhkan versi beta tanpa flag, Composer menolaknya dengan pesan does not match your minimum-stability. Solusinya adalah menaikkan konfigurasi "minimum-stability" di composer.json — misalnya ke "beta" — atau tetap menuliskan versi tidak stabil itu secara eksplisit di root agar otomatis ter-flag.

Memilih ^ atau ~ #

Untuk aplikasi dan library modern, jadikan ^ pilihan utama: penuh perbaikan patch dan fitur minor, aman dari perubahan breaking, dan merupakan bawaan composer require. Gunakan ~ dengan tiga komponen (misalnya ~2.7.0) hanya ketika Anda tahu kenaikan minor sebuah paket berisiko — misalnya framework besar yang rilis minornya kerap mengubah perilaku. Hindari versi eksak dan * untuk require sehari-hari: eksak membekukan perbaikan keamanan, sedangkan * membuka pintu lompatan major tanpa Anda sadari.

Apapun kendala yang kita pilih, hasil resolusinya selalu dibekukan menjadi versi pasti di composer.lock — mekanisme yang sudah kita bahas pada chapter composer.lock . Pertanyaan berikutnya adalah bagaimana Composer menyelesaikan kendala-kendala dari banyak paket sekaligus, termasuk saat kendala mereka saling bertabrakan — topik chapter dependency resolution .

Referensi #