Setiap proyek punya daftar perintah yang diulang terus-menerus: jalankan test, periksa gaya kode, bangun autoloader produksi, bersihkan cache. Menghafal dan mengetik perintah panjang tersebut rawan salah, dan anggota tim baru harus membaca dokumentasi hanya untuk tahu cara menjalankan test. Composer menyediakan solusi bawaan bernama scripts: perintah shortcut yang kita definisikan sendiri di composer.json, plus event hook yang otomatis dijalankan pada titik-titik penting seperti setelah instalasi dependensi. Pada chapter ini kita membangun demo nyata — runner test sederhana beserta shortcut-nya — lalu membedah event, prefix @php, dan cara meneruskan argumen.

Shortcut untuk Perintah Rutin #

Kunci scripts di composer.json memetakan nama perintah menjadi baris perintah yang sesungguhnya. Kita siapkan proyek demo-scripts berisi runner test kecil di tests/run.php:

php
<?php

declare(strict_types=1);

function slug(string $judul): string
{
    $kecil = mb_strtolower(trim($judul));
    $bersih = preg_replace('/[^a-z0-9]+/', '-', $kecil);

    return trim($bersih, '-');
}

$opsi = getopt('', ['filter:']);
$filter = $opsi['filter'] ?? '';

$semuaTes = [
    'slug menormalkan judul' => ['  Belajar PHP Dasar  ', 'belajar-php-dasar'],
    'slug menghapus tanda baca' => ['Halo, Dunia!', 'halo-dunia'],
    'slug teks kosong' => ['   ', ''],
];

$jumlah = 0;
$gagal = 0;

foreach ($semuaTes as $nama => [$masukan, $diharapkan]) {
    if ($filter !== '' && !str_contains($nama, $filter)) {
        continue;
    }
    $jumlah++;
    $hasil = slug($masukan);
    if ($hasil === $diharapkan) {
        echo "OK    - $nama\n";
    } else {
        $gagal++;
        echo "GAGAL - $nama (diharapkan '$diharapkan', didapat '$hasil')\n";
    }
}

echo "\n$jumlah tes dijalankan, $gagal gagal\n";
exit($gagal > 0 ? 1 : 0);

Runner ini menguji satu fungsi slug() terhadap tiga kasus, mencetak hasil per tes, lalu mengembalikan exit code 1 bila ada yang gagal — perilaku penting untuk integrasi CI nanti. Sekarang kita daftarkan shortcut-nya:

json
{
    "name": "budi/demo-scripts",
    "require": {
        "php": ">=8.1"
    },
    "scripts": {
        "test": "php tests/run.php"
    }
}

Setelah itu, perintah composer test menjadi pintasan resmi proyek:

bash
cd demo-scripts
composer test

Output yang Dihasilkan:

text
OK    - slug menormalkan judul
OK    - slug menghapus tanda baca
OK    - slug teks kosong

3 tes dijalankan, 0 gagal

Bentuk eksplisitnya adalah composer run <nama>. Keduanya menjalankan script yang sama, dengan satu perbedaan kecil pada tampilan: composer run test menggemakan perintah aslinya lewat baris > php tests/run.php sebelum outputnya, sedangkan pemanggilan langsung composer test langsung menampilkan hasil:

bash
composer run test

Output yang Dihasilkan:

text
> php tests/run.php
OK    - slug menormalkan judul
OK    - slug menghapus tanda baca
OK    - slug teks kosong

3 tes dijalankan, 0 gagal

Nilai dari shortcut ini tumbuh seiring proyek: composer test tetap satu kata walau runner di belakangnya berganti dari skrip kustom ke PHPUnit, dan anggota tim cukup menghafal satu antarmuka yang sama di semua proyek. Daftar script yang tersedia bisa dilihat kapan saja dengan composer run-script --list:

text
scripts:
  test               Runs the test script as defined in composer.json
  versi              Runs the versi script as defined in composer.json
  post-autoload-dump
  post-install-cmd

Event pre dan post #

Selain shortcut manual, scripts bisa menempel pada event siklus hidup Composer. Nama-nama seperti post-install-cmd bukan perintah yang kita panggil, melainkan pemicu yang Composer jalankan sendiri pada momen tertentu:

EventDijalankan pada
pre-install-cmd / post-install-cmdsebelum / sesudah composer install
pre-update-cmd / post-update-cmdsebelum / sesudah composer update
pre-autoload-dump / post-autoload-dumpsebelum / sesudah autoloader dibangun (termasuk saat install, update, dan dump-autoload)
pre-package-install / post-package-installsetiap satu paket selesai dipasang
pre-package-update / post-package-updatesetiap satu paket selesai diperbarui
pre-package-uninstall / post-package-uninstallsetiap satu paket selesai dihapus
post-root-package-installsetelah paket utama terpasang pada composer create-project
post-create-project-cmdsetelah composer create-project selesai

Kita tambahkan dua event ke composer.json:

json
{
    "name": "budi/demo-scripts",
    "require": {
        "php": ">=8.1"
    },
    "scripts": {
        "test": "php tests/run.php",
        "post-autoload-dump": [
            "echo 'Autoloader diperbarui.'"
        ],
        "post-install-cmd": [
            "echo 'Dependensi terpasang.'"
        ]
    }
}

Nilai script juga boleh berupa array seperti di atas — Composer menjalankan isinya berurutan. Sekarang setiap composer install otomatis mengeksekusi kedua event:

bash
composer install

Output yang Dihasilkan:

text
Installing dependencies from lock file (including require-dev)
Verifying lock file contents can be installed on current platform.
Nothing to install, update or remove
Generating autoload files
> echo 'Autoloader diperbarui.'
Autoloader diperbarui.
> echo 'Dependensi terpasang.'
Dependensi terpasang.

Urutan pada output tersebut mencerminkan urutan event yang sebenarnya: autoloader dibangun lebih dulu (post-autoload-dump menyala tepat setelah Generating autoload files), kemudian proses install ditutup dengan post-install-cmd. Pola seperti ini dipakai framework sungguhan — Laravel misalnya membersihkan cache dan menyalin berkas konfigurasi lewat post-autoload-dump setiap kali dependensi dipasang.

Menjalankan PHP dengan @php #

Baris perintah di dalam scripts dieksekusi lewat shell, dan itu menimbulkan satu pertanyaan praktis: PHP yang mana yang dipakai bila kita menulis php ...? Jawabannya bergantung pada PATH mesin — bisa berbeda antara laptop, server CI, dan produksi. Prefix @php menghilangkan keraguan itu: Composer menjalankan perintah dengan binary PHP yang sama yang sedang menjalankan Composer sendiri:

json
{
    "scripts": {
        "versi": "@php -r 'echo \"PHP \" . PHP_VERSION . PHP_EOL;'"
    }
}
bash
composer versi

Output yang Dihasilkan:

text
PHP 8.4.23

Prefix ini adalah satu-satunya cara yang andal untuk skrip PHP lintas-mesin, karena proyek tidak perlu berasumsi letak binary PHP di PATH. Composer juga mengenali beberapa prefix kerabatnya seperti @putenv untuk menyetel variabel lingkungan dan @trigger-error untuk menaikkan error internal, semuanya dijalankan di dalam proses Composer tanpa membuka shell baru — lebih cepat dan bebas dari keanehan quoting antar-sistem-operasi.

Meneruskan Argumen dengan – #

Runner test sungguhan hampir selalu punya opsi: filter kasus uji, mode verbose, seed tertentu. Argumen tersebut bisa diteruskan dari shortcut dengan pemisah -- — Composer mengambil semua yang tertulis setelah pemisah dan menyambungkannya ke akhir baris perintah script:

bash
composer test -- --filter=baca

Output yang Dihasilkan:

text
OK    - slug menghapus tanda baca

1 tes dijalankan, 0 gagal

Hanya satu tes yang berjalan karena runner kita membaca --filter lewat getopt() lalu menyaring nama tes yang mengandung kata baca. Pemisah -- penting: tanpa pemisah tersebut, Composer akan mencoba membaca opsi sebagai miliknya sendiri dan menolak opsi yang tidak dikenalnya.

Satu perilaku terakhir yang membuat scripts cocok untuk otomasi: exit code script menjadi exit code Composer. Runner kita mengembalikan 1 saat ada tes gagal, sehingga composer test juga berakhir dengan 1 — dan pipeline CI yang menganggap exit code bukan nol sebagai kegagalan akan langsung menandai build merah tanpa konfigurasi tambahan. Kombinasi composer install, composer test, dan pemeriksaan platform — topik chapter berikutnya — adalah rangkaian standar hampir semua pipeline PHP modern.

Referensi #