Jika Anda ingin mencegah kode bermasalah masuk ke repository, Git hook di proyek CodeIgniter 4 adalah cara praktis untuk menahan commit atau push sebelum perubahan lolos pemeriksaan dasar. Pendekatan ini cocok untuk validasi cepat di lokal: formatter/linter, static analysis, dan test penting yang relatif ringan.

Tujuan utamanya bukan menggantikan CI, tetapi memindahkan umpan balik lebih dekat ke developer. Dengan begitu, error sederhana seperti style yang tidak konsisten, type issue yang mudah dideteksi, atau test dasar yang gagal bisa berhenti sebelum masuk ke branch bersama.

Mengapa Git hook relevan untuk CodeIgniter 4

Pada proyek CodeIgniter 4, perubahan sering menyentuh kombinasi controller, service, model, validation, dan konfigurasi. Masalah kecil seperti import yang salah, pemanggilan method yang keliru, atau perubahan perilaku di layer service sering baru terlihat saat test atau review. Git hook membantu menutup celah ini dengan validasi otomatis di mesin developer.

Manfaat yang paling terasa:

  • Feedback cepat: developer tahu masalah sebelum commit atau push.
  • DX lebih baik: aturan kualitas berjalan otomatis, bukan mengandalkan ingatan manual.
  • Branch lebih bersih: mengurangi commit yang jelas rusak.
  • Review lebih fokus: reviewer tidak sibuk mengomentari hal mekanis seperti formatting.

Prinsip penting: hook lokal harus cepat dan dapat diprediksi. Jika terlalu lambat, developer akan frustrasi, mencari cara bypass, atau menonaktifkannya.

Target workflow: apa yang dijalankan di pre-commit dan pre-push

Untuk menjaga hook tetap ringan, pisahkan validasi berdasarkan biaya eksekusi:

Pre-commit

  • Format/lint pada file yang di-stage
  • Static analysis ringan atau terbatas jika memungkinkan
  • Pemeriksaan cepat yang hampir selalu selesai dalam hitungan singkat

Pre-push

  • Static analysis penuh
  • Subset test penting, atau seluruh test suite jika masih cukup cepat

Pemisahan ini bekerja karena commit terjadi jauh lebih sering daripada push. Jadi, pre-commit sebaiknya hanya memuat pemeriksaan yang ringan dan sangat sering berguna.

Struktur dasar proyek dan composer script

Agar hook tidak berisi command yang panjang dan sulit dirawat, simpan semua workflow di composer.json. Hook cukup memanggil script Composer. Keuntungannya:

  • Satu sumber definisi command untuk lokal dan CI
  • Lebih mudah diubah tanpa mengedit banyak file hook
  • Dokumentasi tim lebih sederhana

Contoh struktur yang umum:

project-root/
├── app/
├── public/
├── tests/
├── vendor/
├── tools/
│   └── git-hooks/
├── composer.json
├── phpunit.xml
├── phpstan.neon
└── .php-cs-fixer.php

Contoh composer.json yang memusatkan command quality check:

{
  "scripts": {
    "lint": "php-cs-fixer fix --dry-run --diff --verbose",
    "lint:fix": "php-cs-fixer fix --verbose",
    "stan": "phpstan analyse",
    "test": "phpunit",
    "qa": [
      "@lint",
      "@stan",
      "@test"
    ],
    "hook:pre-commit": "bash tools/git-hooks/run-pre-commit.sh",
    "hook:pre-push": "bash tools/git-hooks/run-pre-push.sh"
  }
}

Jika tim Anda lebih memilih Laravel Pint untuk style fixer karena sintaks yang lebih sederhana, itu juga bisa dipakai pada proyek PHP secara umum selama dependensi dan ruleset sesuai kebutuhan tim. Namun untuk proyek CodeIgniter 4, PHP CS Fixer sering dipilih karena lebih fleksibel dan netral terhadap framework.

Menambahkan tool inti: formatter, static analysis, dan test

1. Formatter/linter: PHP CS Fixer atau Pint

Untuk workflow tim, formatter sebaiknya punya dua mode:

  • Mode cek untuk hook dan CI
  • Mode perbaikan otomatis untuk dijalankan manual oleh developer

Contoh konfigurasi minimal .php-cs-fixer.php:

<?php

$finder = PhpCsFixer\Finder::create()
    ->in([
        __DIR__ . '/app',
        __DIR__ . '/tests'
    ])
    ->name('*.php');

return (new PhpCsFixer\Config())
    ->setRiskyAllowed(false)
    ->setRules([
        '@PSR12' => true,
        'array_syntax' => ['syntax' => 'short'],
        'no_unused_imports' => true,
        'single_quote' => true,
        'trailing_comma_in_multiline' => true,
    ])
    ->setFinder($finder);

Untuk hook, gunakan mode --dry-run agar commit gagal jika format belum benar, tetapi file tidak diam-diam diubah oleh hook. Ini lebih aman dan lebih mudah dipahami developer.

2. Static analysis dengan PHPStan

PHPStan berguna untuk menangkap error yang tidak selalu terlihat dari lint syntax biasa, seperti pemanggilan method yang tidak ada, nullability issue, atau tipe data yang tidak konsisten.

Contoh phpstan.neon yang sederhana:

parameters:
  paths:
    - app
    - tests
  level: 5
  tmpDir: writable/cache/phpstan

Catatan penting: level PHPStan tidak perlu langsung agresif. Pada repository yang sudah berjalan lama, menaikkan level terlalu cepat justru menghambat adopsi. Mulailah dari level yang realistis, lalu tingkatkan bertahap.

3. PHPUnit untuk test dasar

Hook lokal sebaiknya tidak selalu menjalankan semua test jika suite Anda besar. Mulai dari:

  • test unit/service yang cepat
  • test untuk komponen inti yang sering berubah
  • smoke test sederhana untuk memastikan aplikasi tidak rusak secara dasar

Jika Anda sudah punya grouping test, pre-push bisa menjalankan subset yang penting dulu, sedangkan suite penuh dipindahkan ke CI.

Contoh implementasi Git hook yang ringan

Pre-commit: fokus pada file yang di-stage

Masalah paling umum pada hook adalah terlalu lambat karena memeriksa seluruh repository. Untuk pre-commit, lebih efisien memproses file PHP yang sedang di-stage saja.

Contoh tools/git-hooks/run-pre-commit.sh:

#!/usr/bin/env bash
set -e

STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.php$' || true)

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

echo "Menjalankan formatter check pada file yang di-stage..."
php vendor/bin/php-cs-fixer fix --dry-run --diff --path-mode=intersection $STAGED_FILES

echo "Pre-commit selesai."

Mengapa pendekatan ini efektif:

  • Hanya file relevan yang diperiksa
  • Waktu eksekusi jauh lebih singkat
  • Developer tidak dihukum untuk file lama yang belum dirapikan

Jika ingin menambah pemeriksaan syntax dasar, Anda bisa menambahkan loop php -l untuk file yang di-stage. Namun jangan menumpuk terlalu banyak command di pre-commit jika hasilnya lambat.

Pre-push: static analysis dan test dasar

Contoh tools/git-hooks/run-pre-push.sh:

#!/usr/bin/env bash
set -e

echo "Menjalankan PHPStan..."
php vendor/bin/phpstan analyse

echo "Menjalankan PHPUnit..."
php vendor/bin/phpunit

echo "Pre-push selesai."

Jika test suite Anda mulai berat, ubah strategi menjadi:

  • pre-push menjalankan subset test yang cepat
  • CI menjalankan suite penuh, termasuk integrasi dan regresi

Cara memasang hook tanpa setup manual per developer

Masalah klasik Git hook adalah file hook berada di .git/hooks, yang tidak ikut version control. Jika mengandalkan copy manual, cepat atau lambat akan ada anggota tim yang lupa memasang atau menggunakan versi lama.

Pendekatan yang lebih stabil adalah menyimpan hook di repository, lalu mengarahkan Git ke direktori hook tersebut melalui core.hooksPath.

Simpan hook di dalam repo

tools/
└── git-hooks/
    ├── pre-commit
    ├── pre-push
    ├── run-pre-commit.sh
    └── run-pre-push.sh

Contoh file wrapper tools/git-hooks/pre-commit:

#!/usr/bin/env bash
composer hook:pre-commit

Contoh file wrapper tools/git-hooks/pre-push:

#!/usr/bin/env bash
composer hook:pre-push

Lalu arahkan Git ke path tersebut:

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

Otomasi pemasangan hook

Supaya tidak manual, sediakan script bootstrap, misalnya tools/setup-hooks.sh:

#!/usr/bin/env bash
set -e

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

echo "Git hooks berhasil dipasang."

Lalu expose lewat Composer:

{
  "scripts": {
    "setup-hooks": "bash tools/setup-hooks.sh"
  }
}

Developer cukup menjalankan:

composer setup-hooks

Jika ingin lebih jauh, Anda bisa memasukkan langkah ini ke proses onboarding lokal tim atau script setup project. Intinya, jangan menjadikan dokumentasi sebagai satu-satunya mekanisme sinkronisasi.

Alur eksekusi yang disarankan

  1. Developer mengubah file di app/ atau tests/.
  2. Developer menjalankan formatter lokal bila perlu, misalnya composer lint:fix.
  3. Saat git commit, hook pre-commit memeriksa file PHP yang di-stage.
  4. Jika gagal, commit dibatalkan dan developer memperbaiki masalah.
  5. Saat git push, hook pre-push menjalankan PHPStan dan PHPUnit.
  6. Setelah lolos, perubahan dikirim ke remote dan tetap diverifikasi lagi oleh CI.

Model ini memberi dua lapis perlindungan:

  • Lokal untuk feedback cepat
  • CI untuk validasi final yang lebih lengkap dan konsisten

Menghindari hook yang terlalu lambat

Keberhasilan Git hook lebih ditentukan oleh pengalaman pengguna daripada banyaknya rule. Berikut strategi paling efektif agar workflow tetap dipakai tim:

1. Batasi pre-commit ke file yang di-stage

Ini langkah paling berdampak. Jangan scan seluruh project jika kebutuhan Anda hanya mencegah commit rusak yang baru saja dibuat.

2. Pisahkan formatter check dan auto-fix

Hook sebaiknya mengecek, bukan diam-diam mengubah file. Auto-fix tetap tersedia lewat command manual seperti composer lint:fix.

3. Simpan static analysis berat di pre-push atau CI

PHPStan pada codebase besar bisa cukup mahal. Jika pre-push terasa lambat, pertimbangkan:

  • jalankan hanya pada branch utama sebelum push
  • jalankan subset path tertentu
  • jadikan CI sebagai sumber validasi penuh

4. Gunakan cache tool jika tersedia

Beberapa tool memiliki mekanisme cache atau direktori temporary untuk mempercepat eksekusi berikutnya. Pastikan direktori cache tidak bentrok dengan hak akses lokal tim.

5. Jangan jalankan test integrasi berat di hook lokal

Test yang membutuhkan service eksternal, database kompleks, atau environment khusus lebih cocok ditempatkan di CI. Hook lokal idealnya independen dan mudah dijalankan di semua mesin developer.

Kapan validasi dipindah ke CI

Git hook lokal bukan tempat untuk semua jenis pemeriksaan. Pindahkan validasi ke CI jika memenuhi salah satu kondisi berikut:

  • Waktu eksekusinya terlalu lama untuk workflow harian
  • Membutuhkan dependency eksternal seperti database, Redis, atau service lain
  • Butuh environment yang konsisten dan terisolasi
  • Hasilnya harus menjadi source of truth resmi sebelum merge

Contoh yang cocok di CI:

  • seluruh PHPUnit suite
  • integration test
  • coverage gate
  • security scanning
  • matrix build untuk beberapa versi runtime

Aturan praktis: jika sebuah pemeriksaan membuat git commit terasa berat, kemungkinan besar pemeriksaan itu terlalu mahal untuk pre-commit.

Trade-off dan keterbatasan

Kelebihan

  • Mencegah error umum masuk terlalu jauh
  • Mengurangi noise saat code review
  • Memaksa standar kualitas minimum secara konsisten

Kekurangan

  • Hook lokal bisa dibypass dengan --no-verify
  • Performa tergantung mesin developer
  • Tooling yang terlalu agresif bisa mengganggu produktivitas
  • Repository lama sering butuh strategi adopsi bertahap

Karena hook bisa dibypass, CI tetap wajib sebagai pengaman final. Hook lokal berfungsi sebagai lapisan pencegahan awal, bukan pengganti pipeline validasi terpusat.

Kesalahan umum dan tips debugging

Hook tidak berjalan

  • Periksa apakah core.hooksPath sudah diarahkan dengan benar
  • Pastikan file hook executable dengan chmod +x
  • Pastikan shell script menggunakan line ending yang benar

Command gagal hanya di satu mesin

  • Periksa versi PHP lokal dan extension yang terpasang
  • Pastikan dependency sudah di-install melalui Composer
  • Gunakan path executable dari vendor/bin agar konsisten

Formatter memeriksa terlalu banyak file

  • Periksa penggunaan --path-mode=intersection atau logika daftar file staged
  • Pastikan finder/config tidak memasukkan direktori yang tidak relevan

PHPStan terlalu berisik pada codebase lama

  • Turunkan level awal
  • Batasi path yang dianalisis lebih dulu
  • Naikkan standar secara bertahap, bukan sekaligus

Checklist adopsi bertahap untuk repository yang sudah berjalan

Jika project CodeIgniter 4 Anda sudah aktif dan memiliki banyak legacy code, jangan langsung menyalakan semua validasi sekaligus. Gunakan pendekatan bertahap berikut:

  1. Langkah 1: tambahkan formatter check di pre-commit untuk file yang di-stage.
  2. Langkah 2: sediakan command auto-fix agar developer mudah menyesuaikan style.
  3. Langkah 3: tambahkan PHPStan dengan level moderat pada path utama.
  4. Langkah 4: tambahkan PHPUnit subset yang cepat di pre-push.
  5. Langkah 5: pindahkan suite penuh dan validasi berat ke CI.
  6. Langkah 6: setelah tim stabil, tingkatkan rule atau level analysis secara bertahap.

Checklist implementasi praktis:

  • Composer script untuk lint, lint:fix, stan, test, dan setup hook
  • Direktori hook yang disimpan di repository
  • Pre-commit ringan, fokus ke file staged
  • Pre-push untuk analysis dan test dasar
  • Dokumentasi onboarding satu perintah
  • CI tetap aktif sebagai validasi final

Penutup

Git hook untuk CodeIgniter 4 paling efektif jika dirancang sederhana: formatter di pre-commit, static analysis dan test dasar di pre-push, lalu validasi penuh di CI. Dengan composer script yang rapi dan sinkronisasi melalui core.hooksPath, tim bisa mendapatkan automasi lokal yang konsisten tanpa bergantung pada setup manual yang mudah terlewat.

Mulailah dari workflow yang ringan dan benar-benar membantu. Jika hook terasa cepat, jelas, dan jarang memberi false positive, peluang besar seluruh tim akan memakainya secara konsisten.