GitHub Actions untuk Laravel sering melambat bukan karena test-nya berat, tetapi karena instalasi dependency berulang, job yang terlalu gemuk, dan strategi cache yang kurang tepat. Solusi yang paling aman biasanya bukan sekadar "menyalakan cache", melainkan memilih apa yang layak di-cache, memisahkan jenis pekerjaan CI, dan menjalankan test pada beberapa versi PHP dengan matrix yang terkontrol.
Dalam artikel ini, kita akan membangun workflow Laravel yang praktis: lint, static analysis, dan test dipisah menjadi job terpisah; Composer dioptimalkan dengan cache yang relevan; dan pengujian lintas versi PHP dilakukan dengan matrix. Kita juga akan membahas kapan cache benar-benar membantu, kapan justru menimbulkan hasil tidak konsisten, serta cara menjaga durasi CI tetap rendah saat repository membesar.
Mengapa pipeline Laravel sering lambat dan tidak stabil
Pada banyak project Laravel, bottleneck CI biasanya muncul dari beberapa hal berikut:
- Composer install berulang di setiap job dan setiap run.
- Satu job melakukan semuanya: install dependency, lint, PHPStan, PHPUnit, build asset, dan sebagainya.
- Cache yang terlalu agresif, misalnya menyimpan folder
vendortanpa key yang benar. - Perbedaan versi PHP antara lingkungan lokal dan CI.
- Matrix yang tidak dikendalikan, sehingga kombinasi job membengkak dan waktu total meningkat drastis.
Tujuan pipeline yang baik bukan hanya cepat, tetapi juga deterministik. Jika commit yang sama kadang lolos dan kadang gagal karena cache lama, maka CI kehilangan nilai utamanya sebagai sinyal kualitas.
Prinsip desain workflow GitHub Actions untuk Laravel
1. Pisahkan job berdasarkan tujuan
Pisahkan minimal menjadi tiga jenis job:
- Lint untuk format dan style, misalnya Laravel Pint.
- Static analysis untuk PHPStan.
- Test untuk PHPUnit, biasanya dengan matrix beberapa versi PHP.
Keuntungan pemisahan ini:
- Lebih mudah melihat sumber kegagalan.
- Job ringan seperti Pint bisa selesai cepat tanpa menunggu test berat.
- Cache dan dependency bisa diatur sesuai kebutuhan tiap job.
- Lebih mudah mengoptimalkan job yang paling mahal.
2. Cache yang aman lebih penting daripada cache yang agresif
Untuk Laravel berbasis Composer, cache yang paling aman biasanya adalah cache download Composer, bukan langsung cache vendor. Composer cache menyimpan paket yang sudah diunduh, tetapi proses instalasi tetap menghormati composer.lock, platform PHP, dan dependency resolution yang relevan.
Sebaliknya, cache vendor bisa lebih cepat dalam kondisi tertentu, tetapi juga lebih rawan menghasilkan state yang tidak cocok jika:
- versi PHP berbeda,
- ekstensi PHP berbeda,
composer.lockberubah,- script post-install memodifikasi isi directory,
- ada dependency native atau perilaku spesifik platform.
Prinsip praktis: Mulai dari cache Composer download terlebih dahulu. Tambahkan cache
vendorhanya jika benar-benar perlu dan key-nya dibuat cukup ketat.
3. Gunakan matrix PHP secara selektif
Matrix berguna untuk memastikan aplikasi Laravel Anda tetap berjalan pada beberapa versi PHP yang didukung. Namun tidak semua job perlu dijalankan pada semua versi.
Contoh strategi yang masuk akal:
- Pint: cukup satu versi PHP.
- PHPStan: cukup satu versi PHP yang menjadi baseline tim, kecuali ada alasan kompatibilitas khusus.
- PHPUnit: jalankan di beberapa versi PHP melalui matrix.
Pendekatan ini menekan waktu total CI tanpa kehilangan cakupan kompatibilitas yang penting.
Contoh workflow GitHub Actions untuk Laravel
Berikut contoh workflow yang memisahkan job lint, static analysis, dan test. Contoh ini menggunakan:
actions/checkoutuntuk mengambil source code,shivammathur/setup-phpuntuk menyiapkan runtime PHP,actions/cacheuntuk Composer cache,- matrix PHP untuk job test.
name: ci
on:
pull_request:
push:
branches:
- main
jobs:
lint:
name: Lint (Pint)
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.2'
coverage: none
- name: Get Composer cache directory
id: composer-cache
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
- name: Cache Composer downloads
uses: actions/cache@v4
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-php-8.2-composer-${{ hashFiles('**/composer.lock') }}
restore-keys: |
${{ runner.os }}-php-8.2-composer-
${{ runner.os }}-composer-
- name: Install dependencies
run: composer install --prefer-dist --no-interaction --no-progress
- name: Run Pint
run: ./vendor/bin/pint --test
static-analysis:
name: Static Analysis (PHPStan)
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.2'
coverage: none
- name: Get Composer cache directory
id: composer-cache
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
- name: Cache Composer downloads
uses: actions/cache@v4
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-php-8.2-composer-${{ hashFiles('**/composer.lock') }}
restore-keys: |
${{ runner.os }}-php-8.2-composer-
${{ runner.os }}-composer-
- name: Install dependencies
run: composer install --prefer-dist --no-interaction --no-progress
- name: Run PHPStan
run: ./vendor/bin/phpstan analyse --no-progress
test:
name: PHPUnit (PHP ${{ matrix.php }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
php: ['8.1', '8.2', '8.3']
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
coverage: none
- name: Get Composer cache directory
id: composer-cache
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
- name: Cache Composer downloads
uses: actions/cache@v4
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-php-${{ matrix.php }}-composer-${{ hashFiles('**/composer.lock') }}
restore-keys: |
${{ runner.os }}-php-${{ matrix.php }}-composer-
${{ runner.os }}-composer-
- name: Install dependencies
run: composer install --prefer-dist --no-interaction --no-progress
- name: Prepare environment
run: |
cp .env.example .env
php artisan key:generate
- name: Run PHPUnit
run: php artisan testWorkflow di atas sengaja dibuat konservatif. Ia sudah cukup cepat untuk banyak project Laravel, tetapi tetap aman karena tidak bergantung pada cache vendor yang rentan stale.
Memahami key cache dan restore-keys
Struktur key yang baik
Bagian paling penting dari cache adalah key. Pada contoh di atas:
key: ${{ runner.os }}-php-${{ matrix.php }}-composer-${{ hashFiles('**/composer.lock') }}Artinya cache dibedakan berdasarkan:
- OS runner, misalnya Ubuntu.
- Versi PHP, karena dependency dan platform requirement bisa berbeda.
- Isi composer.lock, sehingga saat dependency berubah, key juga berubah.
Ini penting karena cache yang terlalu umum mudah menyebabkan mismatch.
Kegunaan restore-keys
restore-keys memungkinkan GitHub Actions mencari cache yang paling mendekati jika key utama tidak ditemukan. Contohnya:
restore-keys: |
${{ runner.os }}-php-${{ matrix.php }}-composer-
${{ runner.os }}-composer-Dengan pola ini, jika composer.lock berubah, Actions masih bisa mencoba cache Composer lain untuk OS dan versi PHP yang sama. Untuk cache download Composer, pendekatan ini umumnya aman karena Composer tetap memverifikasi dependency yang dibutuhkan saat install.
Namun untuk cache vendor, penggunaan restore-keys perlu lebih hati-hati. Cache lama yang "mirip" belum tentu valid untuk state dependency terbaru.
Kapan cache Composer membantu, dan kapan cache vendor berbahaya
Cache Composer: pilihan default yang aman
Cache Composer membantu terutama saat:
- job CI sering berjalan pada pull request,
- dependency cukup banyak,
- job terpisah memerlukan
composer installmasing-masing, - runner bersifat ephemeral sehingga tidak ada state lokal antar-run.
Cache ini mempercepat pengunduhan paket, tetapi Composer tetap membangun vendor dari lock file yang ada. Karena itu, hasilnya cenderung konsisten.
Cache vendor: cepat, tetapi harus disiplin
Menyimpan vendor atau artifact hasil install bisa mengurangi waktu lebih jauh, terutama jika beberapa job membutuhkan dependency yang sama. Tetapi pendekatan ini aman hanya jika:
- key memasukkan
composer.lock, OS, dan versi PHP, - job penerima menggunakan lingkungan yang kompatibel,
- Anda memahami efek script Composer dan file generated lain,
- dependency tidak bergantung pada state yang berubah antar-job.
Masalah umum cache vendor:
- Autoload stale setelah perubahan dependency.
- Binary tool tidak cocok dengan versi PHP berbeda dalam matrix.
- State tersisa dari job sebelumnya jika folder dimodifikasi setelah install.
- False green: CI lolos karena memakai
vendorlama yang kebetulan masih jalan.
Jika prioritas Anda adalah stabilitas, gunakan cache download Composer. Jika prioritas Anda adalah kecepatan maksimal, Anda boleh mengevaluasi cache
vendoratau artifact, tetapi buat key sangat spesifik dan hindari memakainya lintas versi PHP.
Kapan artifact lebih cocok daripada cache
Untuk berbagi hasil install antar-job dalam satu workflow run, artifact kadang lebih tepat daripada cache. Cache dirancang untuk dipakai ulang antar-run, sedangkan artifact lebih cocok untuk memindahkan output dari satu job ke job lain pada run yang sama.
Contoh kapan artifact masuk akal:
- Anda punya job awal yang melakukan
composer install. - Job berikutnya pada versi PHP yang sama hanya perlu memakai hasil itu.
- Anda ingin menghindari instalasi ulang dalam run yang sama.
Tetapi tetap ada trade-off: upload/download artifact juga memerlukan waktu. Untuk project kecil, langkah tambahan ini sering tidak memberi keuntungan nyata.
Strategi matrix PHP yang efisien
Gunakan fail-fast dengan sadar
Pada contoh workflow, kita memakai:
strategy:
fail-fast: falseJika fail-fast bernilai false, kegagalan pada satu versi PHP tidak langsung membatalkan kombinasi lain. Ini berguna saat Anda ingin melihat cakupan kegagalan penuh, misalnya apakah bug hanya terjadi di PHP tertentu.
Trade-off-nya: runner tetap menghabiskan waktu sampai seluruh matrix selesai. Untuk repository besar dengan antrean CI panjang, sebagian tim memilih fail-fast: true agar feedback awal lebih cepat. Pilihan terbaik bergantung pada tujuan pipeline:
- Feedback lengkap lintas versi → pakai
false. - Hentikan biaya CI secepat mungkin saat sudah jelas gagal → pertimbangkan
true.
Jangan mematrix semua job
Kesalahan umum adalah menjalankan Pint, PHPStan, dan PHPUnit di semua versi PHP. Hasilnya, jumlah job meledak tanpa manfaat sebanding. Untuk sebagian besar project Laravel:
- Pint cukup satu versi.
- PHPStan cukup satu versi yang sesuai baseline tim atau target produksi utama.
- PHPUnit yang paling layak dimatrix.
Pilih versi PHP berdasarkan dukungan nyata
Jangan menambah versi PHP ke matrix hanya karena tersedia. Masukkan hanya versi yang memang didukung oleh aplikasi dan dependency Anda. Matrix yang terlalu luas memperlambat CI dan menambah noise debugging.
Integrasi dasar dengan PHPUnit, Pint, dan PHPStan
PHPUnit melalui Artisan atau binary langsung
Di project Laravel, php artisan test biasanya nyaman dipakai karena terintegrasi dengan pengalaman Laravel. Jika Anda butuh opsi spesifik PHPUnit, Anda juga bisa memanggil binary secara langsung. Yang penting, command CI harus sama atau mendekati command yang dipakai tim sehari-hari agar perilaku lebih konsisten.
Pint sebagai quality gate cepat
./vendor/bin/pint --test adalah pilihan umum untuk memastikan style sesuai aturan tanpa mengubah file di CI. Menempatkan Pint pada job terpisah membuat feedback style muncul cepat dan tidak tertutup oleh log test yang panjang.
PHPStan untuk bug yang tidak tertangkap test
PHPStan berguna untuk menemukan masalah tipe, penggunaan API yang keliru, dan asumsi yang rapuh. Dalam CI, pastikan baseline dan konfigurasi sudah stabil di repository. Hindari menjalankan analisis dengan konfigurasi yang berbeda antara lokal dan CI, karena itu sering memunculkan hasil yang membingungkan.
Menjaga waktu CI tetap rendah saat project membesar
1. Kurangi duplikasi setup
Jika banyak job memiliki langkah yang sama, pertimbangkan ekstraksi ke reusable workflow atau composite action internal. Tujuannya bukan sekadar rapi, tetapi agar perubahan setup dilakukan satu kali dan tidak menyebabkan inkonsistensi antar-job.
2. Evaluasi dependency dev yang berat
Semakin banyak tool analisis, semakin lama composer install. Pastikan semua dependency dev memang bernilai. Tool yang tidak dipakai aktif akan tetap membebani CI.
3. Jalankan jenis pekerjaan pada frekuensi yang tepat
Tidak semua pemeriksaan harus berjalan pada setiap event dengan tingkat yang sama. Contohnya, job dasar bisa berjalan pada setiap pull request, sementara pemeriksaan lebih mahal dijalankan pada push ke branch utama atau jadwal tertentu. Ini keputusan operasional yang bergantung pada kebutuhan tim.
4. Hindari kerja yang tidak perlu dalam job test
Untuk job PHPUnit yang tidak memerlukan browser, jangan tambahkan layanan dan setup tambahan yang tidak dipakai. Semakin sedikit langkah, semakin rendah peluang gagal karena faktor non-test.
5. Pantau job paling mahal
Lihat run history dan identifikasi job yang paling sering memakan waktu. Optimasi terbaik hampir selalu datang dari bottleneck nyata, bukan dari semua bagian sekaligus.
Debugging saat cache membuat hasil CI tidak konsisten
Jika Anda melihat gejala seperti "lokal lolos, CI kadang gagal" atau hasil berbeda antar-run, periksa area berikut:
- Apakah key cache cukup spesifik? Sertakan OS, versi PHP, dan
composer.lock. - Apakah Anda memakai restore-keys terlalu longgar? Ini aman untuk Composer download cache, tetapi berisiko untuk
vendor. - Apakah folder cache berisi hasil generated file? File generated bisa stale.
- Apakah command install selalu dijalankan? Cache seharusnya membantu, bukan menggantikan validasi dependency.
- Apakah matrix membagi versi PHP yang berbeda dengan cache sama? Ini sumber mismatch yang sangat umum.
Langkah debugging praktis:
- Matikan sementara cache pada job yang bermasalah.
- Bandingkan hasilnya dengan run yang memakai cache.
- Jika stabil tanpa cache, perketat key atau ubah strategi menjadi hanya cache Composer download.
- Pastikan tidak ada file dalam workspace yang dimodifikasi lalu ikut tersimpan sebagai cache/artifact tanpa sengaja.
Contoh kapan memakai artifact vendor dengan aman
Jika Anda benar-benar ingin menghindari composer install berulang pada beberapa job dengan versi PHP yang sama, pola berikut bisa dipertimbangkan:
- Satu job prepare-dependencies melakukan install.
- Folder
vendordi-upload sebagai artifact. - Job lint dan static-analysis yang memakai versi PHP sama mengunduh artifact tersebut.
Tetapi jangan campur artifact vendor itu ke job test dengan versi PHP lain dalam matrix. Ini justru membuka peluang error yang sulit dilacak.
Jika Anda memilih pola ini, dokumentasikan dengan jelas alasan dan batasannya di repository agar tim paham bahwa artifact tersebut hanya valid untuk kombinasi environment tertentu.
Checklist implementasi
- Gunakan workflow terpisah atau job terpisah untuk Pint, PHPStan, dan PHPUnit.
- Gunakan Composer download cache sebagai optimasi awal yang aman.
- Buat key cache dengan kombinasi OS + versi PHP + hash composer.lock.
- Gunakan restore-keys untuk Composer cache secara konservatif.
- Jalankan matrix PHP terutama pada job test, bukan semua job.
- Tentukan fail-fast sesuai kebutuhan: hemat waktu atau ingin laporan lengkap lintas versi.
- Hindari berbagi
vendorantar-job yang memakai versi PHP berbeda. - Pastikan command CI serupa dengan command yang dipakai developer secara lokal.
- Tinjau ulang durasi job secara berkala saat dependency dan jumlah test bertambah.
Kesalahan umum yang perlu dihindari
- Meng-cache
vendordengan key terlalu umum. - Menjalankan semua job pada semua versi PHP.
- Mengandalkan cache sebagai pengganti install yang benar.
- Tidak memasukkan
composer.lockke key cache. - Mengaktifkan terlalu banyak langkah setup di job yang sederhana.
- Tidak memisahkan lint, static analysis, dan test.
- Menganggap cache selalu mempercepat. Pada project kecil, kompleksitas cache tambahan kadang tidak sepadan.
Penutup
GitHub Actions untuk Laravel yang cepat dan konsisten biasanya dibangun dari keputusan kecil yang disiplin: cache Composer dengan key yang benar, matrix PHP hanya untuk job yang memang perlu, dan pemisahan lint, static analysis, serta test agar sinyal CI jelas. Mulailah dari setup yang sederhana dan stabil, lalu optimalkan berdasarkan bottleneck nyata.
Jika Anda ragu antara kecepatan dan konsistensi, pilih konsistensi terlebih dahulu. CI yang sedikit lebih lambat tetapi deterministik hampir selalu lebih berharga daripada pipeline cepat yang sesekali memberi hasil menyesatkan.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!