Pre-commit hook di Laravel berguna untuk menangkap masalah paling umum sebelum kode masuk ke repository: style yang tidak konsisten, error statis sederhana, dan test yang jelas-jelas gagal. Dengan kombinasi Laravel Pint, PHPStan, dan Pest, tim bisa mempercepat umpan balik lokal tanpa menunggu pipeline CI berjalan.

Poin pentingnya bukan menggantikan CI, tetapi memindahkan sebagian validasi ke mesin developer agar bug sederhana berhenti lebih awal. Jika hook dibuat ringan, relevan, dan konsisten, developer experience tetap baik dan kualitas commit naik tanpa terasa menghambat.

Mengapa pre-commit hook layak dipakai di Laravel

Mengandalkan CI saja memang cukup untuk validasi akhir, tetapi ada jeda antara commit, push, dan hasil pipeline. Untuk masalah yang sifatnya lokal dan cepat dideteksi, jeda ini tidak efisien. Pre-commit hook cocok untuk memblokir hal-hal yang seharusnya tidak pernah lolos dari laptop developer.

  • Pint mencegah commit dengan format yang tidak sesuai standar tim.
  • PHPStan membantu menangkap kesalahan tipe, nullability, properti/metode yang tidak ada, atau asumsi kode yang lemah.
  • Pest menjalankan test cepat untuk memastikan perubahan tidak langsung merusak perilaku dasar.

Workflow ini paling berguna untuk tim kecil sampai menengah yang ingin kualitas commit konsisten tanpa menambah proses review manual untuk masalah-masalah sepele.

Kapan pre-commit lebih tepat daripada hanya CI

Pre-commit lebih tepat saat Anda ingin:

  • Menghentikan commit yang jelas bermasalah sebelum masuk ke remote.
  • Mengurangi noise di pull request akibat formatting atau error statis dasar.
  • Memberi feedback dalam hitungan detik, bukan menit.
  • Menjaga kebiasaan tim agar validasi dasar selalu dijalankan.

Sebaliknya, CI tetap wajib untuk validasi penuh, karena hook lokal bisa dibypass, bisa tidak terpasang di semua mesin, dan tidak selalu identik dengan environment server.

Prinsip workflow pre-commit yang cepat

Masalah terbesar pre-commit hook biasanya bukan teknis, tetapi kecepatan. Jika setiap commit memicu seluruh suite test dan analisis penuh project, developer akan merasa workflow ini mengganggu. Karena itu, desain hook sebaiknya mengikuti beberapa prinsip sederhana.

  1. Jalankan check paling murah lebih dulu. Format dan static analysis ringan biasanya lebih cepat daripada test.
  2. Batasi scope ke file yang berubah bila tool mendukung dan implementasinya tetap aman.
  3. Hindari validasi berat di hook lokal, simpan validasi penuh di CI.
  4. Berikan output yang jelas agar developer tahu apa yang harus diperbaiki.
  5. Fail fast: hentikan proses saat satu check gagal.

Tujuan hook bukan memverifikasi seluruh aplikasi secara sempurna, melainkan menghentikan commit yang paling mungkin menyebabkan masalah langsung.

Tools yang dipakai

Laravel Pint

Pint adalah formatter kode PHP untuk ekosistem Laravel. Untuk pre-commit, Pint sangat cocok karena cepat dan hasilnya deterministik. Jika memungkinkan, jalankan hanya pada file PHP yang berubah agar waktu eksekusinya tetap pendek.

PHPStan

PHPStan memberi lapisan analisis statis yang berguna untuk menemukan bug sebelum runtime. Di hook lokal, Anda bisa menjalankannya terhadap file yang berubah atau subset tertentu. Namun perlu diingat, analisis per-file tidak selalu setara dengan analisis penuh project karena ada dependensi lintas file, autoload, dan konteks type inference yang lebih luas.

Pest

Pest dipakai untuk test yang cepat dan nyaman dibaca. Di pre-commit, idealnya Anda tidak menjalankan seluruh test suite kecuali project-nya kecil. Pilihan yang lebih realistis adalah menjalankan subset test cepat, misalnya unit test atau test yang relevan dengan area yang sering berubah, lalu serahkan test lengkap ke CI.

Struktur implementasi: simpan hook di repository

Praktik yang umum dan aman adalah menyimpan script hook di dalam repository, lalu mengarahkan Git ke direktori tersebut. Ini lebih baik daripada meminta tiap developer menyalin file hook manual ke .git/hooks, karena isi hook menjadi ter-versioning dan mudah diperbarui.

Contoh struktur direktori

project-root/
├── app/
├── bootstrap/
├── tests/
├── tools/
│   └── git-hooks/
│       └── pre-commit
├── composer.json
└── phpstan.neon

Mengarahkan Git ke direktori hook

Jalankan sekali di repository lokal:

git config core.hooksPath tools/git-hooks
chmod +x tools/git-hooks/pre-commit

Dengan pendekatan ini, tim cukup pull perubahan hook dari repository tanpa perlu mengelola file tersembunyi di dalam .git.

Catatan: karena konfigurasi core.hooksPath bersifat lokal pada clone Git, tetap sediakan langkah setup di README atau script onboarding tim.

Contoh pre-commit hook untuk Laravel

Berikut contoh hook yang relatif ringan. Hook ini:

  • Mengambil daftar file yang di-stage.
  • Memilih hanya file PHP.
  • Menjalankan Pint pada file yang berubah.
  • Menjalankan PHPStan dengan target file yang berubah bila ada.
  • Menjalankan subset test Pest yang cepat sebagai pagar minimum.
#!/usr/bin/env sh

set -e

STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM)
PHP_FILES=$(printf "%s\n" "$STAGED_FILES" | grep -E '\.php$' || true)

if [ -z "$PHP_FILES" ]; then
  echo "Tidak ada file PHP yang di-stage. Lewati hook."
  exit 0
fi

echo "Menjalankan Laravel Pint pada file yang berubah..."
printf "%s\n" "$PHP_FILES" | xargs ./vendor/bin/pint --dirty

echo "Menjalankan PHPStan..."
printf "%s\n" "$PHP_FILES" | xargs ./vendor/bin/phpstan analyse --memory-limit=1G

echo "Menjalankan test cepat dengan Pest..."
./vendor/bin/pest tests/Unit

echo "Pre-commit selesai."

Contoh di atas menunjukkan arah implementasi, tetapi ada beberapa hal yang perlu dipahami sebelum dipakai apa adanya.

Mengapa urutannya seperti itu

Pint dijalankan lebih dulu karena paling murah dan sering kali bisa memperbaiki masalah otomatis. Jika formatting gagal atau mengubah file, developer bisa meninjau hasilnya lalu melakukan git add ulang bila diperlukan.

PHPStan diletakkan setelah formatter karena analisis statis lebih mahal. Menjalankannya setelah code style rapi juga membuat output lebih nyaman dibaca.

Pest diletakkan terakhir karena test biasanya paling mahal. Jika formatting atau static analysis sudah gagal, tidak ada manfaat menjalankan test lebih lanjut.

Catatan penting tentang file yang di-stage

Hook pre-commit idealnya memvalidasi isi yang benar-benar akan di-commit, yaitu staged snapshot, bukan sekadar file kerja di working directory. Implementasi sederhana sering membaca file yang sedang ada di disk, sehingga jika developer punya perubahan yang belum di-stage, hasil hook bisa berbeda dari isi commit sebenarnya.

Untuk banyak tim kecil, kompromi ini masih dapat diterima karena lebih mudah dirawat. Namun jika Anda membutuhkan akurasi lebih tinggi, pertimbangkan pendekatan yang memvalidasi staged content secara eksplisit atau gunakan tool tambahan yang memang dirancang untuk skenario ini.

Strategi menjalankan check hanya pada file yang berubah

Menjalankan check pada file yang berubah adalah cara paling realistis untuk menjaga hook tetap cepat. Tetapi tiap tool punya trade-off sendiri.

Pint: kandidat terbaik untuk mode incremental

Formatter adalah kandidat paling aman untuk dijalankan pada file yang berubah. Bahkan jika scope terbatas ke file tertentu, hasilnya tetap valid karena formatting umumnya tidak bergantung pada konteks seluruh project.

Jika tim Anda ingin hasil paling sederhana, cukup jalankan Pint pada file PHP yang di-stage. Ini biasanya memberi rasio manfaat-bandwidth terbaik.

PHPStan: incremental boleh, tapi pahami batasannya

Analisis statis berdasarkan file yang berubah memang mempercepat hook, tetapi tidak selalu mendeteksi efek perubahan terhadap area lain. Contohnya, mengubah kontrak class, generic type, atau return type bisa memengaruhi file lain yang tidak ikut dianalisis.

Rekomendasinya:

  • Gunakan PHPStan incremental di pre-commit untuk feedback cepat.
  • Jalankan analisis penuh di CI agar perubahan lintas modul tetap terdeteksi.

Pest: pilih subset test yang cepat

Menghubungkan file yang berubah ke test yang spesifik bisa dilakukan, tetapi kompleksitasnya cepat naik. Untuk mayoritas tim, strategi yang lebih sederhana adalah:

  • Jalankan test unit cepat di pre-commit.
  • Jalankan seluruh test suite di CI.

Pendekatan ini lebih mudah dijelaskan ke tim dan lebih stabil daripada mencoba membuat mapping otomatis file-ke-test yang rapuh.

Contoh versi hook yang lebih konservatif

Jika Anda ingin implementasi yang lebih mudah dirawat, gunakan hook yang fokus pada dua hal: formatting file yang berubah dan validasi minimum yang konsisten.

#!/usr/bin/env sh

set -e

STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM)
PHP_FILES=$(printf "%s\n" "$STAGED_FILES" | grep -E '\.php$' || true)

if [ -n "$PHP_FILES" ]; then
  echo "[1/3] Pint"
  ./vendor/bin/pint --dirty

  echo "[2/3] PHPStan"
  ./vendor/bin/phpstan analyse --memory-limit=1G app tests

  echo "[3/3] Pest"
  ./vendor/bin/pest tests/Unit
fi

Hook ini tidak benar-benar incremental untuk PHPStan dan Pest, tetapi sering kali lebih stabil. Jika codebase masih kecil atau menengah, pendekatan seperti ini kadang lebih baik daripada optimasi agresif yang sulit dipelihara.

CI tetap jadi sumber kebenaran akhir

Pre-commit hook sebaiknya dianggap sebagai lapisan pertama, bukan satu-satunya lapisan validasi. Ada beberapa alasan mengapa CI tetap wajib:

  • Hook bisa dilewati dengan git commit --no-verify.
  • Tidak semua developer mungkin sudah mengaktifkan hook.
  • Environment lokal dan CI bisa berbeda.
  • Validasi penuh project sering terlalu berat untuk dijalankan setiap commit secara lokal.

Karena itu, di CI Anda tetap perlu menjalankan validasi lengkap, misalnya:

  • Pint dalam mode verifikasi.
  • PHPStan terhadap seluruh codebase.
  • Pest untuk seluruh test suite yang relevan.

Prinsip praktis: pre-commit untuk feedback cepat, CI untuk kepastian penuh.

Trade-off yang perlu dipahami

Keuntungan

  • Feedback lebih cepat daripada menunggu CI.
  • Pull request lebih bersih karena masalah style dan error dasar sudah tertangani.
  • Review code lebih fokus pada desain, logika bisnis, dan arsitektur.
  • Kebiasaan kualitas lebih mudah dibentuk karena validasi berjalan otomatis.

Kekurangan

  • Bisa terasa lambat jika hook terlalu banyak menjalankan proses berat.
  • Bisa dibypass, sehingga tidak cukup sebagai satu-satunya kontrol kualitas.
  • Butuh perawatan saat struktur project atau tooling berubah.
  • Incremental analysis tidak selalu lengkap, terutama untuk static analysis dan test.

Masalah umum dan cara mengatasinya

1. Hook terlalu lambat

Ini keluhan paling umum. Penyebabnya biasanya:

  • Menjalankan seluruh test suite di setiap commit.
  • Menjalankan PHPStan penuh pada codebase besar.
  • Menambahkan terlalu banyak tool sekaligus.

Solusinya:

  • Mulai dari Pint + subset test atau Pint + PHPStan.
  • Batasi test di pre-commit ke unit test cepat.
  • Simpan validasi berat di pre-push atau CI.

2. Hook mudah di-bypass

Ini memang sifat Git hook lokal. Anda tidak bisa mengandalkan hook sebagai mekanisme keamanan mutlak. Solusinya bukan memaksa hook menjadi kompleks, tetapi memastikan CI menegakkan aturan yang sama atau lebih ketat.

3. Pint mengubah file saat commit gagal

Ini normal. Formatter bisa memperbaiki file, tetapi perubahan tersebut belum tentu otomatis ter-stage. Setelah itu developer perlu meninjau hasilnya, lalu menjalankan git add ulang sebelum commit kembali.

4. PHPStan gagal pada file tertentu padahal kode terasa benar

Sering kali masalahnya ada pada:

  • Autoload yang belum sinkron.
  • Stub atau baseline yang kurang tepat.
  • Anotasi type yang tidak konsisten.
  • Perubahan konteks Laravel seperti container binding, magic property, atau helper yang belum dijelaskan ke analyzer.

Langkah debugging yang berguna:

  • Jalankan command yang sama secara manual di terminal.
  • Periksa output lengkap tanpa hook.
  • Pastikan dependency dan autoload sudah diperbarui.
  • Kurangi optimasi incremental sementara untuk memastikan masalahnya bukan pada scope file.

5. Test Pest di hook terlalu flakey

Jika test sering gagal karena dependency eksternal, waktu, database state, atau environment lokal, jangan paksa test tersebut masuk ke pre-commit. Simpan hanya test yang cepat dan stabil. Hook harus bisa dipercaya; kalau terlalu sering gagal karena alasan non-deterministik, developer akan cenderung mem-bypass.

Rekomendasi implementasi bertahap untuk tim kecil sampai menengah

Pendekatan bertahap biasanya lebih berhasil daripada langsung memasang workflow yang kompleks.

Tahap 1: mulai dari Pint

Gunakan pre-commit hanya untuk formatting. Ini paling mudah diterima tim karena manfaatnya langsung terasa dan jarang menimbulkan friksi besar.

Tahap 2: tambah PHPStan dengan scope terbatas

Setelah tim nyaman, tambahkan PHPStan. Jika codebase masih kecil, analisis ke direktori utama seperti app dan tests masih masuk akal. Jika mulai terasa lambat, evaluasi apakah perlu mode incremental atau cukup mengandalkan CI untuk analisis penuh.

Tahap 3: tambah Pest untuk subset test cepat

Pilih test yang stabil dan cepat, biasanya unit test. Hindari dulu feature test berat yang memerlukan setup database atau service tambahan jika itu membuat commit terasa lambat.

Tahap 4: dokumentasikan dan standarkan

Sediakan panduan singkat di README:

  • Cara mengaktifkan hook.
  • Apa saja yang dijalankan hook.
  • Cara memperbaiki kegagalan umum.
  • Kapan boleh memakai --no-verify dan kapan tidak.

Standar yang jelas mencegah kebingungan dan mengurangi resistensi dari tim.

Rekomendasi praktis yang paling masuk akal

Untuk mayoritas tim Laravel kecil sampai menengah, konfigurasi berikut biasanya memberi hasil terbaik:

  • Pre-commit: Pint + PHPStan ringan atau Pint + unit test cepat.
  • CI: Pint check penuh + PHPStan penuh + seluruh suite Pest.
  • Opsional: pindahkan validasi yang lebih berat ke pre-push jika pre-commit mulai terasa mengganggu.

Jika harus memilih urutan adopsi, mulai dari Pint, lalu PHPStan, lalu Pest. Urutan ini paling aman untuk menjaga DX tetap baik sambil menaikkan kualitas commit secara bertahap.

Penutup

Laravel pre-commit hook dengan Pint, PHPStan, dan Pest efektif jika diperlakukan sebagai pagar cepat di lokal, bukan pengganti CI. Kuncinya adalah menjaga hook tetap ringan, memilih validasi yang benar-benar bernilai, dan menerima bahwa verifikasi penuh tetap milik pipeline CI.

Jika workflow ini diterapkan bertahap dan didokumentasikan dengan baik, tim akan mendapat dua keuntungan sekaligus: feedback lebih cepat saat menulis kode, dan lebih sedikit bug atau masalah sepele yang lolos ke remote.