Jika Anda ingin memastikan aplikasi CodeIgniter 4 tetap kompatibel di beberapa versi PHP dan database, CI Pipeline Matriks PHP dan Database di GitHub Actions adalah pendekatan yang paling masuk akal. Dengan matrix build, satu workflow dapat menjalankan kombinasi pengujian berbeda secara paralel, misalnya PHP 8.x dengan MySQL dan PostgreSQL, sehingga bug kompatibilitas terdeteksi lebih cepat.

Panduan ini fokus pada alur kerja yang benar-benar dipakai tim: composer install dengan cache, setup environment untuk test, migrasi database, seed minimum, lalu eksekusi PHPUnit dan pengecekan coding standard. Selain itu, kita juga akan membahas cara memisahkan job cepat dan job berat, penggunaan service container, pengelolaan secret, pengumpulan artifact log saat gagal, serta cara mencegah false negative di CI.

Mengapa pipeline matriks penting untuk CodeIgniter 4

Aplikasi CodeIgniter 4 sering bergantung pada kombinasi runtime dan database yang berbeda antara lingkungan pengembangan, staging, dan produksi. Masalah yang umum muncul antara lain:

  • Query atau migrasi berjalan di MySQL tetapi gagal di PostgreSQL.
  • Perbedaan perilaku ekstensi atau dependency pada versi PHP tertentu.
  • Test lulus di lokal, tetapi gagal di CI karena environment tidak konsisten.
  • Pipeline terlalu lambat karena semua pemeriksaan dijalankan dalam satu job besar.

Matrix build membantu mengatasi masalah itu dengan menjalankan kombinasi parameter secara otomatis. Namun, matrix yang terlalu besar juga bisa memperlambat feedback dan menambah biaya komputasi. Karena itu, desain pipeline harus seimbang: cukup luas untuk menangkap masalah kompatibilitas, tetapi tetap fokus agar hasil cepat didapat.

Desain pipeline yang disarankan

Untuk kebanyakan proyek CodeIgniter 4, pisahkan workflow menjadi dua kategori:

1. Job cepat

  • Install dependency dengan cache.
  • Lint atau coding standard.
  • Test unit yang tidak memerlukan database berat.

Tujuannya memberi feedback awal dalam beberapa menit. Jika coding standard gagal, developer tidak perlu menunggu job database selesai.

2. Job berat

  • Test integrasi atau feature test dengan database.
  • Matrix lintas versi PHP dan driver database.
  • Migrasi dan seed data minimum.

Pemisahan ini membuat pipeline lebih efisien. Kegagalan sederhana ditangkap lebih cepat, sementara pengujian yang lebih mahal hanya dijalankan pada job yang memang memerlukannya.

Struktur workflow GitHub Actions

Letakkan file workflow di .github/workflows/ci.yml. Struktur umumnya seperti ini:

  • name: nama workflow.
  • on: event yang memicu workflow, misalnya push dan pull_request.
  • jobs: kumpulan job terpisah seperti lint dan test-matrix.
  • strategy.matrix: kombinasi PHP dan database.
  • services: container MySQL atau PostgreSQL.
  • steps: urutan aksi seperti checkout, setup PHP, install dependency, migrasi, dan test.

Contoh workflow berikut cukup realistis untuk proyek CodeIgniter 4 yang ingin menguji coding standard dan integrasi database.

name: ci-codeigniter4

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main, develop]

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Setup PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: '8.2'
          coverage: none
          tools: composer

      - name: Cache Composer dependencies
        uses: actions/cache@v4
        with:
          path: |
            vendor
            ~/.composer/cache/files
          key: ${{ runner.os }}-php-8.2-composer-${{ hashFiles('**/composer.lock') }}
          restore-keys: |
            ${{ runner.os }}-php-8.2-composer-

      - name: Install dependencies
        run: composer install --no-interaction --prefer-dist

      - name: Run coding standard
        run: composer cs

  test-matrix:
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        php: ['8.1', '8.2']
        database: [mysql, pgsql]

    services:
      mysql:
        image: mysql:8
        env:
          MYSQL_ROOT_PASSWORD: root
          MYSQL_DATABASE: ci4_test
          MYSQL_USER: ci4
          MYSQL_PASSWORD: secret
        ports:
          - 3306:3306
        options: >-
          --health-cmd="mysqladmin ping -h 127.0.0.1 -proot"
          --health-interval=10s
          --health-timeout=5s
          --health-retries=10

      pgsql:
        image: postgres:15
        env:
          POSTGRES_DB: ci4_test
          POSTGRES_USER: ci4
          POSTGRES_PASSWORD: secret
        ports:
          - 5432:5432
        options: >-
          --health-cmd="pg_isready -U ci4 -d ci4_test"
          --health-interval=10s
          --health-timeout=5s
          --health-retries=10

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Setup PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: ${{ matrix.php }}
          coverage: none
          tools: composer
          extensions: intl, mbstring, pdo, pdo_mysql, pdo_pgsql

      - name: Cache Composer dependencies
        uses: actions/cache@v4
        with:
          path: |
            vendor
            ~/.composer/cache/files
          key: ${{ runner.os }}-php-${{ matrix.php }}-composer-${{ hashFiles('**/composer.lock') }}
          restore-keys: |
            ${{ runner.os }}-php-${{ matrix.php }}-composer-

      - name: Install dependencies
        run: composer install --no-interaction --prefer-dist

      - name: Prepare test environment
        run: |
          cp env .env
          php -r "file_exists('.env') || exit(1);"

      - name: Configure MySQL test env
        if: matrix.database == 'mysql'
        run: |
          {
            echo "database.tests.hostname=127.0.0.1";
            echo "database.tests.database=ci4_test";
            echo "database.tests.username=ci4";
            echo "database.tests.password=secret";
            echo "database.tests.DBDriver=MySQLi";
            echo "database.tests.port=3306";
          } >> .env

      - name: Configure PostgreSQL test env
        if: matrix.database == 'pgsql'
        run: |
          {
            echo "database.tests.hostname=127.0.0.1";
            echo "database.tests.database=ci4_test";
            echo "database.tests.username=ci4";
            echo "database.tests.password=secret";
            echo "database.tests.DBDriver=Postgre";
            echo "database.tests.port=5432";
          } >> .env

      - name: Wait for database service
        run: sleep 10

      - name: Run migrations
        run: php spark migrate --all

      - name: Seed minimum test data
        run: php spark db:seed TestSeeder

      - name: Run PHPUnit
        run: vendor/bin/phpunit

      - name: Upload logs on failure
        if: failure()
        uses: actions/upload-artifact@v4
        with:
          name: logs-php-${{ matrix.php }}-${{ matrix.database }}
          path: |
            writable/logs
            tests/_output

Contoh di atas menunjukkan pola penting, tetapi Anda tetap perlu menyesuaikan nama group database test, perintah coding standard, dan seeder dengan struktur proyek Anda.

Menyiapkan environment test untuk CodeIgniter 4

Gunakan konfigurasi terpisah untuk test

Jangan pakai konfigurasi database development untuk CI. Buat environment test yang eksplisit agar test tidak menulis ke database yang salah. Cara paling aman adalah mengisi nilai koneksi test di file .env saat workflow berjalan.

Pastikan test Anda mengarah ke group database yang memang dipakai untuk pengujian. Jika proyek Anda menggunakan konfigurasi berbeda, sesuaikan key environment dan nama group-nya.

Migrasi sebelum test

Menjalankan php spark migrate --all memastikan skema database di CI selalu konsisten dengan source code terbaru. Ini lebih aman dibanding mengandalkan dump SQL statis yang bisa tertinggal dari migrasi terakhir.

Seed data minimum

Gunakan seeder kecil yang hanya memasukkan data yang dibutuhkan test. Hindari seed data besar karena:

  • Menambah durasi pipeline.
  • Membuat test sulit dipahami.
  • Meningkatkan risiko flaky test karena data terlalu kompleks.

Contoh pendekatan seeder minimum:

<?php

namespace App\Database\Seeds;

use CodeIgniter\Database\Seeder;

class TestSeeder extends Seeder
{
    public function run()
    {
        $this->db->table('users')->insert([
            'email' => '[email protected]',
            'name'  => 'CI Tester',
        ]);
    }
}

Seeder seperti ini cukup untuk test login, authorization, atau query sederhana tanpa menambah beban berlebih.

Composer cache dan percepatan feedback

Pada banyak pipeline PHP, bottleneck paling umum adalah instalasi dependency. Karena itu, cache Composer hampir selalu layak diaktifkan.

Apa yang sebaiknya di-cache

  • ~/.composer/cache/files untuk cache package Composer.
  • vendor jika Anda ingin menghindari unduh ulang dependency pada kombinasi yang sama.

Trade-off menyimpan vendor adalah ukuran cache lebih besar. Untuk tim kecil, ini sering masih masuk akal jika dependency stabil dan workflow cukup sering berjalan. Jika ukuran cache mulai menjadi masalah, minimal cache direktori Composer saja.

Mengapa key cache harus spesifik

Gunakan composer.lock dalam key cache. Jika dependency berubah, cache lama tidak dipakai secara membabi buta. Ini mencegah hasil CI yang tidak konsisten akibat cache usang.

Memisahkan job cepat dan job berat

Jangan campur semua pemeriksaan ke satu job. Secara praktis, struktur berikut lebih sehat:

  • lint: coding standard, syntax check, atau static analysis ringan.
  • test-matrix: hanya test yang benar-benar membutuhkan kombinasi PHP dan database.

Keuntungan pendekatan ini:

  • Feedback awal datang lebih cepat.
  • Kegagalan mudah dilokalisasi.
  • Retry lebih murah karena Anda bisa menjalankan ulang job tertentu.

Jika proyek makin besar, Anda bisa menambah satu job lagi untuk unit test only tanpa database. Dengan begitu, bug logika dasar terdeteksi sebelum test integrasi dijalankan.

Service container MySQL dan PostgreSQL

GitHub Actions mendukung service container yang berjalan berdampingan dengan job. Untuk test CodeIgniter 4, ini cocok karena Anda bisa menyediakan database sementara tanpa server eksternal.

MySQL

Gunakan image resmi dan definisikan health check. Health check penting agar job tidak langsung menjalankan migrasi saat database belum siap menerima koneksi.

PostgreSQL

Konsepnya sama. Pastikan driver PHP yang dibutuhkan sudah dipasang pada step setup PHP, dan sesuaikan konfigurasi driver database di aplikasi.

Kesalahan umum adalah hanya menunggu beberapa detik dengan sleep lalu langsung menjalankan migrasi. Ini kadang berhasil, kadang gagal. Jika pipeline sering flaky, prioritaskan health check yang jelas dan langkah verifikasi koneksi sebelum migrasi.

Pengelolaan secret dengan aman

Untuk service container lokal di workflow, Anda bisa menggunakan kredensial sederhana yang hanya hidup selama job berlangsung. Namun, jika pipeline membutuhkan layanan eksternal seperti database staging, API private, atau token package registry, simpan nilainya di GitHub Secrets, bukan di repository.

Prinsip dasarnya:

  • Jangan hardcode password produksi di workflow YAML.
  • Bedakan secret untuk CI dan untuk environment lain.
  • Berikan akses seminimal mungkin.

Contoh penggunaan secret di workflow:

env:
  APP_ENV: testing
  EXTERNAL_API_TOKEN: ${{ secrets.EXTERNAL_API_TOKEN }}

Jika test tidak membutuhkan layanan eksternal, lebih baik jangan memasukkan secret sama sekali. Pipeline akan lebih sederhana dan lebih aman.

Artifact log saat gagal

Saat test database gagal, output console sering tidak cukup. Simpan log aplikasi sebagai artifact agar bisa diunduh setelah job selesai.

Pola yang umum:

  • Upload writable/logs saat job gagal.
  • Upload output test jika framework test Anda membuat file hasil khusus.
  • Berikan nama artifact yang memuat versi PHP dan database agar mudah ditelusuri.

Ini sangat membantu saat ada perbedaan perilaku antar kombinasi matrix.

Mencegah false negative di CI

False negative adalah kondisi ketika pipeline gagal bukan karena bug aplikasi, tetapi karena masalah environment, timing, atau test yang rapuh. Ini salah satu sumber friksi terbesar dalam CI.

Penyebab umum

  • Database service belum siap saat migrasi dijalankan.
  • Test bergantung pada urutan eksekusi.
  • Data test tidak dibersihkan dengan benar.
  • Timezone, locale, atau ekstensi PHP berbeda dari lokal.
  • Query yang bergantung pada perilaku spesifik satu database.

Cara menguranginya

  • Gunakan migrasi dan seed yang deterministik.
  • Pastikan setiap test menginisialisasi data yang dibutuhkannya sendiri.
  • Jangan bergantung pada data sisa dari test lain.
  • Tambahkan health check dan validasi koneksi database.
  • Batasi matrix hanya pada kombinasi yang benar-benar didukung aplikasi.

Jika test Anda hanya mendukung subset fitur tertentu di PostgreSQL atau MySQL, dokumentasikan dengan jelas. Jangan biarkan pipeline memberi sinyal gagal yang sebenarnya berasal dari asumsi test yang salah.

Tips debugging saat pipeline gagal

1. Baca step pertama yang gagal, bukan gejala terakhir

Banyak developer langsung fokus ke PHPUnit, padahal akar masalahnya bisa ada di composer install, konfigurasi .env, atau migrasi.

2. Verifikasi konfigurasi environment yang benar-benar dipakai

Jika perlu, tampilkan subset variabel yang aman untuk memastikan driver dan host database sudah sesuai. Hindari mencetak secret sensitif.

3. Uji konektivitas database sebelum migrasi

Jika kegagalan sering terjadi di tahap awal, tambahkan langkah verifikasi koneksi. Ini membantu membedakan apakah masalah ada di database service atau di migrasi aplikasi.

4. Periksa log CodeIgniter 4

File di writable/logs sering memberikan stack trace atau pesan query yang lebih lengkap dibanding output test.

5. Reproduksi kombinasi yang gagal

Jika kombinasi PHP 8.1 + PostgreSQL gagal, fokus reproduksi pada kombinasi itu, bukan seluruh matrix. Ini mempercepat investigasi.

Trade-off matrix yang terlalu besar

Secara teori Anda bisa menguji banyak versi PHP, beberapa database, dan berbagai opsi dependency. Secara praktik, matrix besar cepat menjadi mahal dan lambat.

Dampak negatif matrix berlebihan

  • Waktu tunggu pull request lebih lama.
  • Antrian runner meningkat.
  • Debugging lebih rumit karena terlalu banyak kombinasi.
  • Biaya komputasi bertambah tanpa nilai yang setara.

Cara mengendalikannya

  • Uji semua kombinasi penuh hanya pada branch utama atau jadwal harian.
  • Untuk pull request, jalankan subset kombinasi paling penting.
  • Pisahkan job compatibility dari job wajib merge.

Pendekatan ini menjaga kualitas tanpa mengorbankan kecepatan feedback harian.

Rekomendasi pipeline minimum untuk tim kecil

Jika tim Anda kecil dan ingin setup yang realistis, mulai dari konfigurasi minimum berikut:

  • Job 1: coding standard pada satu versi PHP stabil.
  • Job 2: PHPUnit tanpa matrix besar, misalnya satu versi PHP + satu database utama yang dipakai produksi.
  • Job 3 opsional: compatibility matrix terbatas, misalnya satu versi PHP tambahan dan satu driver database tambahan, dijalankan pada pull request penting atau branch utama.

Contoh minimum yang masuk akal:

  • Lint di PHP 8.2.
  • Test utama di PHP 8.2 + MySQL.
  • Compatibility tambahan di PHP 8.1 + PostgreSQL.

Dengan pola ini, Anda sudah mendapat sebagian besar manfaat CI matriks tanpa membuat workflow terlalu berat.

Kesimpulan

CodeIgniter 4: CI Pipeline Matriks PHP dan Database di GitHub Actions paling efektif bila dirancang untuk memberi feedback cepat sekaligus menangkap masalah kompatibilitas nyata. Kuncinya bukan membuat matrix sebesar mungkin, tetapi memilih kombinasi yang relevan, memisahkan job cepat dan job berat, menggunakan cache Composer, menyiapkan environment test yang deterministik, dan menyimpan log saat gagal.

Mulailah dari pipeline kecil yang stabil, lalu perluas secara bertahap ketika kebutuhan kompatibilitas bertambah. CI yang cepat dan konsisten biasanya lebih berguna bagi tim dibanding pipeline kompleks yang sering gagal karena masalah environment, bukan karena bug aplikasi.