PHPackages                             jatimprovcsirt/panda-php - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. jatimprovcsirt/panda-php

ActiveLibrary

jatimprovcsirt/panda-php
========================

PANDA (Private And Secure Data Access) SDK for PHP

v0.1.6(2w ago)09MITPHPPHP ^8.1CI failing

Since Jul 11Pushed 2w agoCompare

[ Source](https://github.com/jatimprovcsirt/panda-php)[ Packagist](https://packagist.org/packages/jatimprovcsirt/panda-php)[ RSS](/packages/jatimprovcsirt-panda-php/feed)WikiDiscussions main Synced 1w ago

READMEChangelogDependencies (7)Versions (8)Used By (0)

PANDA PHP SDK
=============

[](#panda-php-sdk)

**PANDA (Private And Secure Data Access)** — AES-256-GCM field-level encryption SDK for PHP applications.

Requirements
------------

[](#requirements)

- PHP ≥ 8.1 with `ext-openssl`
- Composer

Installation
------------

[](#installation)

Install the package via Composer:

```
composer require jatimprovcsirt/panda-php
```

---

1. PANDA Master Key Initialization (Langkah Pertama yang Wajib)
---------------------------------------------------------------

[](#1-panda-master-key-initialization-langkah-pertama-yang-wajib)

Sebelum menulis kode integrasi apapun, Anda harus menghasilkan kunci master lokal untuk aplikasi Anda. Ini adalah gerbang keamanan satu kali sebelum PANDA bisa beroperasi.

### Langkah 1: Dapatkan PANDA Access Token

[](#langkah-1-dapatkan-panda-access-token)

Anda harus memiliki PANDA Access Token untuk menginisialisasi kunci master aplikasi Anda.

**Daftar aplikasi dan dapatkan token di:**

Note

Setiap aplikasi memerlukan token unik. Token digunakan sebagai gerbang keamanan satu kali untuk inisialisasi kunci.

### Langkah 2: Jalankan Perintah Init

[](#langkah-2-jalankan-perintah-init)

Jalankan perintah berikut di direktori root proyek Anda. Ganti `YOUR_TOKEN` dengan PANDA Access Token yang diperoleh dari portal (panjang: 48 atau 64 karakter hex):

```
# Menggunakan binary CLI dari vendor (setelah composer install)
php vendor/bin/panda init --token YOUR_TOKEN
```

Setelah menjalankan perintah ini, wizard akan:

1. Memvalidasi token Anda
2. Meminta Anda memasukkan Key Identifier (KID) untuk kunci pertama
3. Menghasilkan kunci 256-bit yang aman secara kriptografis secara **lokal**
4. Menambahkan kunci dalam format base64 ke file `.env` Anda (misal: `PANDA_KEY_OPD_DUKCAPIL_KEY_1=...`)

Note

Untuk pengembangan lokal, Anda dapat menggunakan demo token bawaan: `panda_demo_76d08573d453cdbdb4bf5704d13f8f54d57cd3c47c9b4be2`

### Langkah 2: Apa yang Terjadi?

[](#langkah-2-apa-yang-terjadi)

Perintah `init` melakukan hal berikut:

1. Memvalidasi format token Anda.
2. Menghasilkan kunci 256-bit yang aman secara kriptografis secara **lokal** (token tidak pernah menerima kunci ini).
3. Secara otomatis menambahkan kunci dalam format base64 ke file `.env` Anda (misal: `PANDA_KEY_OPD_DUKCAPIL_KEY_1=...`).

### Langkah 3: Restart Server/Terminal (Penting!)

[](#langkah-3-restart-serverterminal-penting)

Important

Karena PHP membaca variabel environment dari `.env` saat startup:

- **Jika menjalankan web server** (misal `php artisan serve`, Apache, Nginx, Docker, php-fpm), Anda **HARUS merestart server/container** agar variabel environment baru terbaca.
- **Jika menjalankan perintah di terminal/CMD**, Anda **HARUS membuka terminal baru** atau reload variabel environment.

### Menambahkan KID/Kunci Tambahan

[](#menambahkan-kidkunci-tambahan)

Jika aplikasi Anda membutuhkan lebih dari satu kunci (misalnya untuk bidang data yang berbeda atau rotasi secara bertahap), gunakan perintah `generate` untuk menambahkan kunci tambahan tanpa memerlukan token:

```
# Kunci kedua: untuk field nomor HP
php vendor/bin/panda generate opd-dukcapil-key-2

# Kunci untuk blind index (suffix -idx wajib dipisah dari kunci enkripsi)
php vendor/bin/panda generate opd-dukcapil-key-1-idx
```

Note

Perintah `generate` hanya dapat digunakan setelah inisialisasi awal dengan `init`. Tidak perlu token lagi untuk menambahkan kunci baru. Setelah kunci tersimpan di `.env`, operasi enkripsi/dekripsi sehari-hari **tidak pernah** menghubungi portal lagi.

### Rotasi Kunci (Tanpa Portal)

[](#rotasi-kunci-tanpa-portal)

Untuk merotasi kunci pada KID yang sudah ada, gunakan `rotate-key`. Perintah ini berjalan **sepenuhnya lokal** tanpa memerlukan token:

```
# Rotasi: buat kunci baru dan set sebagai default KID aktif
php vendor/bin/panda rotate-key --new-kid opd-dukcapil-key-2
```

### Menghapus Kunci (Crypto-Shred)

[](#menghapus-kunci-crypto-shred)

```
# Hapus kunci secara permanen
# PERINGATAN: Data yang dienkripsi dengan kunci ini tidak bisa didekripsi lagi!
php vendor/bin/panda shred opd-dukcapil-key-old
```

### Referensi CLI

[](#referensi-cli)

#### Mode Non-Interaktif (Flags untuk CI/CD)

[](#mode-non-interaktif-flags-untuk-cicd)

Semua perintah CLI dapat digunakan dalam mode non-interaktif dengan menggunakan flag. Ini sangat berguna untuk:

- **CI/CD pipelines** - Otomatisasi tanpa interaksi pengguna
- **Docker containers** - Inisialisasi otomatis saat container start
- **Infrastructure as Code** - Integrasi dengan Terraform, Ansible, dll
- **Automated testing** - Setup dan teardown otomatis

##### Perintah Init - Mode Non-Interaktif

[](#perintah-init---mode-non-interaktif)

```
# Quick init dengan Local provider (mode tercepat)
php vendor/bin/panda init --quick --token  --kid default-key

# Init tanpa generate kunci pertama (untuk automation lanjutan)
php vendor/bin/panda init --quick --token  --skip-key-gen

# Reinitialize paksa (overwrite konfigurasi yang sudah ada)
php vendor/bin/panda init --quick --token  --reinit
```

**Flag `init` yang tersedia:**

FlagShortDefaultDeskripsi`--token ``-t`-PANDA access token (atau set `PANDA_ACCESS_TOKEN` env)`--kid ``-k``default-key`Key Identifier untuk kunci pertama`--quick``-q``false`Skip wizard, gunakan quick setup dengan local provider`--reinit`-`false`Reinitialize meskipun sudah pernah di-init sebelumnya`--skip-key-gen`-`false`Skip generate kunci pertama##### Perintah Generate - Mode Non-Interaktif

[](#perintah-generate---mode-non-interaktif)

```
# Generate kunci dengan KID langsung (non-interactive)
php vendor/bin/panda generate my-new-key

# Generate dengan description
php vendor/bin/panda generate nik-key --description "Key for NIK encryption"
```

**Flag `generate` yang tersedia:**

FlagShortDefaultDeskripsi`--description ``-d`-Deskripsi untuk kunci##### Perintah Shred - Mode Non-Interaktif

[](#perintah-shred---mode-non-interaktif)

```
# Hapus kunci dengan konfirmasi otomatis (hati-hati!)
php vendor/bin/panda shred old-deprecated-key --force
```

**Flag `shred` yang tersedia:**

FlagShortDefaultDeskripsi`--force``-f``false`Skip konfirmasi prompt (irreversible!)##### Perintah Migrate - Mode Non-Interaktif

[](#perintah-migrate---mode-non-interaktif)

```
# Migrasi batch processing dengan flags
php vendor/bin/panda migrate citizens nik default-key \
  --batch-size 500 \
  --delay 200 \
  --pk id \
  --dry-run

# Migrasi PostgreSQL dengan batch size custom
php vendor/bin/panda migrate users email email-key --batch-size 1000

# Migrasi dengan custom primary key
php vendor/bin/panda migrate transactions transaction_id txn-key --pk uuid
```

**Flag `migrate` yang tersedia:**

FlagShortDefaultDeskripsi`--batch-size ``-b``100`Jumlah baris per chunk`--delay ``-d``100`Delay antar chunk (ms)`--pk ``-p``id`Nama kolom primary key`--dry-run`-`false`Estimasi scope tanpa mengubah data##### Tabel Lengkap Referensi CLI

[](#tabel-lengkap-referensi-cli)

PerintahMemerlukan TokenMode Non-InteraktifDeskripsi`php vendor/bin/panda init --token `✅ Ya✅ `--quick`Inisialisasi PANDA (wizard atau quick mode)`php vendor/bin/panda generate `❌ Tidak✅ (default non-interactive)Generate kunci tambahan`php vendor/bin/panda status`❌ Tidak✅ (default non-interactive)Tampilkan status konfigurasi`php vendor/bin/panda config`❌ Tidak✅ (default non-interactive)Tampilkan detail konfigurasi lengkap`php vendor/bin/panda deinit`❌ Tidak✅ (default non-interactive)Hapus konfigurasi PANDA`php vendor/bin/panda rotate-key --new-kid `❌ Tidak✅ (default non-interactive)Rotasi kunci ke KID baru`php vendor/bin/panda shred `❌ Tidak✅ `--force`Hapus kunci permanen`php vendor/bin/panda migrate   `❌ Tidak✅ (default non-interactive)Migrasi plaintext → encrypted---

Infisical Setup (Production-Ready Alternative)
----------------------------------------------

[](#infisical-setup-production-ready-alternative)

Untuk deployment produksi, disarankan menggunakan Infisical sebagai KeyProvider alih-alih LocalKeyProvider (env vars).

### Setup Infisical Cloud

[](#setup-infisical-cloud)

1. **Buat Akun Infisical**

    - Daftar di  (free tier tersedia)
    - Buat project baru
    - Buat environment (dev, staging, prod)
2. **Buat Machine Identity**

    - Buka "Machine Identities" di project Anda
    - Klik "Create Machine Identity"
    - Pilih environment yang diakses
    - Copy Client ID dan Client Secret
3. **Upload Kunci Enkripsi**

    - Buka "Secrets" di project
    - Pilih environment
    - Klik "Add Secret"
    - Secret Key: `nik-key` (atau KID Anda)
    - Secret Value: `` (generate dengan `openssl rand -base64 32`)
4. **Konfigurasi Aplikasi**

    ```
    # .env
    PANDA_KEY_PROVIDER_DRIVER=infisical
    PANDA_INFISICAL_SITE_URL=https://app.infisical.com
    PANDA_INFISICAL_CLIENT_ID=
    PANDA_INFISICAL_CLIENT_SECRET=
    PANDA_INFISICAL_PROJECT_ID=
    PANDA_INFISICAL_ENVIRONMENT=dev
    PANDA_INFISICAL_CACHE_TTL=300
    ```

### Menggunakan InfisicalKeyProvider dalam Kode

[](#menggunakan-infisicalkeyprovider-dalam-kode)

```
use Panda\Crypto\InfisicalKeyProvider;
use Panda\Crypto\FieldCipher;

// Baca dari environment variables atau config
$keyProvider = new InfisicalKeyProvider(
    siteUrl: 'https://app.infisical.com',
    clientId: $_ENV['PANDA_INFISICAL_CLIENT_ID'],
    clientSecret: $_ENV['PANDA_INFISICAL_CLIENT_SECRET'],
    projectId: $_ENV['PANDA_INFISICAL_PROJECT_ID'],
    environment: $_ENV['PANDA_INFISICAL_ENVIRONMENT'],
    cacheTTL: 300 // optional
);

$cipher = new FieldCipher($keyProvider);

// Sisa kode tetap sama
$envelope = $cipher->encrypt("3201012501990001", "nik-key");
```

**Keuntungan Infisical:**

- ✅ Production-ready dengan auto-renewal (tanpa token expiration)
- ✅ UI modern yang mudah digunakan
- ✅ Free tier untuk project kecil
- ✅ Cloud atau self-hosted options

Untuk setup lengkap Infisical, lihat [Infisical Setup Guide](../../docs/infisical-setup-guide.md).

---

2. Vanilla PHP Integration Guide
--------------------------------

[](#2-vanilla-php-integration-guide)

Follow these steps to integrate PANDA in a standard PHP project without any frameworks.

### Step 1: Load Environment Variables

[](#step-1-load-environment-variables)

Make sure your project loads the `.env` file (using a library like `vlucas/phpdotenv` or similar):

```
require_once __DIR__ . '/vendor/autoload.php';

$dotenv = Dotenv\Dotenv::createImmutable(__DIR__);
$dotenv->load();
```

### Step 2: Initialize KeyProvider and FieldCipher

[](#step-2-initialize-keyprovider-and-fieldcipher)

Instantiate the service classes:

```
use Panda\Crypto\LocalKeyProvider;
use Panda\Crypto\FieldCipher;

// 1. KeyProvider reads PANDA_KEY_... from the environment
$keyProvider = new LocalKeyProvider();

// 2. FieldCipher handles encryption, decryption, and auditing
$cipher = new FieldCipher($keyProvider);
```

### Step 3: Encrypt Data (Before DB Storage)

[](#step-3-encrypt-data-before-db-storage)

Encrypt plaintext before writing it to a `TEXT` database column:

```
$rawNik = '3201012501990001';
$kid = 'opd-dukcapil-key-1';

$envelope = $cipher->encrypt($rawNik, $kid);
$jsonForDatabase = $envelope->toJson();
// Store $jsonForDatabase in your database!
```

### Step 4: Decrypt Data (From DB Query)

[](#step-4-decrypt-data-from-db-query)

Retrieve the JSON envelope from the database and decrypt it:

```
use Panda\Crypto\Envelope;

$row = $db->query("SELECT nik FROM citizens WHERE id = 1")->fetch();
$envelope = Envelope::fromJson($row['nik']);

// A. Decrypt with default masking (for safe displaying/logs)
$masked = $cipher->decrypt($envelope);
echo $masked; // Output: "32************01"

// B. Decrypt raw unmasked plaintext (for exports/audited API use)
$raw = $cipher->decryptRaw($envelope);
echo $raw; // Output: "3201012501990001"
```

### Step 5: Setup Blind Index for Search Lookups

[](#step-5-setup-blind-index-for-search-lookups)

To query encrypted columns, you must compute and store a blind index hash in a separate database column (e.g. `nik_bidx` VARCHAR(64) indexed):

```
use Panda\BlindIndex\BlindIndexer;

// Note: The blind index key is distinct and uses the "-idx" suffix
$blindIndexKid = 'opd-dukcapil-key-1-idx';
$blindIndexKey = $keyProvider->getKey($blindIndexKid);

// 1. Compute blind index during INSERT/UPDATE
$blindIndexValue = BlindIndexer::generate($rawNik, $blindIndexKey, 'digits');

// Store both $jsonForDatabase in 'nik' AND $blindIndexValue in 'nik_bidx'!

// 2. Querying by NIK
$searchQuery = '3201012501990001';
$searchHash = BlindIndexer::generate($searchQuery, $blindIndexKey, 'digits');

// Execute SQL lookup:
$stmt = $pdo->prepare("SELECT * FROM citizens WHERE nik_bidx = :hash");
$stmt->execute(['hash' => $searchHash]);
$results = $stmt->fetchAll();
```

---

3. Laravel Integration Guide
----------------------------

[](#3-laravel-integration-guide)

Laravel integration provides automation using Eloquent casts, traits, and validation rules.

### Step 1: Install Package and Publish Configuration

[](#step-1-install-package-and-publish-configuration)

First, register configuration placeholders and set up local environment variables:

```
php artisan panda:install
```

This:

1. Copies `config/panda.php` to your application config directory.
2. Appends `PANDA_DEFAULT_KID=default-key` and `PANDA_ACCESS_TOKEN=` to your `.env` file.

### Step 2: Initialize Key

[](#step-2-initialize-key)

Run the `init` command to generate your local master key (as detailed in Section 1):

```
php vendor/bin/panda init --token YOUR_TOKEN
```

The wizard will prompt you for your Key Identifier (KID). Enter `default-key` when prompted.

Important

**Restart your Laravel development server** (e.g. stop and restart `php artisan serve` or restart Docker containers) so Laravel can read the new environment keys.

### Step 3: Generate Database Columns

[](#step-3-generate-database-columns)

Create a migration for your encrypted field and its blind index column using the helper command:

```
php artisan panda:make-field Citizen nik
```

This creates a migration file in `database/migrations/` which alters the target column to `TEXT` and adds a `nik_bidx` index column. Apply it:

```
php artisan migrate
```

### Step 4: Configure the Eloquent Model

[](#step-4-configure-the-eloquent-model)

Open your Model file (e.g. `app/Models/Citizen.php`) and add the `HasEncryptedFields` trait and `EncryptedCast` cast:

```
namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Panda\Integrations\Laravel\EncryptedCast;
use Panda\Integrations\Laravel\HasEncryptedFields;

class Citizen extends Model
{
    use HasEncryptedFields;

    protected $casts = [
        // Specify the key identifier (KID) to encrypt this column
        'nik' => EncryptedCast::class . ':default-key',
    ];

    // Configure the automatic blind indexing for search lookups:
    protected $encryptedFields = [
        'nik' => [
            'kid' => 'default-key',
            'blind_index' => true,             // Automatically syncs 'nik_bidx'
            'normalization' => 'digits',       // Normalization function (e.g. digits, lowercase, trim)
        ],
    ];
}
```

### Step 5: Save and Query Data

[](#step-5-save-and-query-data)

Now, encryption, decryption, and blind indexing are completely automated!

#### Saving

[](#saving)

```
$citizen = new Citizen();
$citizen->nik = '3201012501990001';
$citizen->save(); // Automatically saves encrypted envelope in 'nik' and blind index in 'nik_bidx'
```

#### Reading

[](#reading)

```
$citizen = Citizen::first();
echo $citizen->nik; // Output: "32************01" (Masked by default)

// Get raw decrypted value
echo $citizen->getRawEncrypted('nik'); // Output: "3201012501990001"
```

#### Querying (Search)

[](#querying-search)

```
// Use the whereEncrypted query scope
$citizens = Citizen::whereEncrypted('nik', '3201012501990001')->get();
```

#### Controller Uniqueness Validation

[](#controller-uniqueness-validation)

Validate that an encrypted column is unique before saving using the `UniqueEncrypted` rule:

```
use Panda\Integrations\Laravel\Rules\UniqueEncrypted;

$request->validate([
    'nik' => ['required', new UniqueEncrypted('citizens', 'nik')],
]);
```

---

4. CodeIgniter 4 Integration Guide
----------------------------------

[](#4-codeigniter-4-integration-guide)

Follow these steps to integrate PANDA in a CodeIgniter 4 application.

### Step 1: Initialize Key &amp; Restart

[](#step-1-initialize-key--restart)

Initialize your local master key using the CLI command:

```
php vendor/bin/panda init --token YOUR_TOKEN
```

The wizard will prompt you for your Key Identifier (KID). Enter `opd-dukcapil-key-1` when prompted.

Then, **restart your development server** (e.g. `spark serve` or php-fpm container).

### Step 2: Access the Service Container

[](#step-2-access-the-service-container)

In CodeIgniter 4, PANDA registers as an optional service. Load the `FieldCipher` instance:

```
use Panda\Integrations\CodeIgniter\PandaService;

$cipher = PandaService::fieldCipher();
```

### Step 3: Insert Encrypted Data with Blind Index

[](#step-3-insert-encrypted-data-with-blind-index)

When saving a record, encrypt the plaintext field and generate the blind index manually before saving:

```
use Panda\BlindIndex\BlindIndexer;
use Panda\Crypto\LocalKeyProvider;

$rawNik = '3201012501990001';
$kid = 'opd-dukcapil-key-1';

// 1. Encrypt raw text
$envelope = $cipher->encrypt($rawNik, $kid);
$encryptedJson = $envelope->toJson();

// 2. Generate blind index hash
$keyProvider = new LocalKeyProvider();
$blindIndexKey = $keyProvider->getKey("{$kid}-idx");
$blindIndexHash = BlindIndexer::generate($rawNik, $blindIndexKey, 'digits');

// 3. Save to Database
$db = \Config\Database::connect();
$db->table('citizens')->insert([
    'nik'      => $encryptedJson,
    'nik_bidx' => $blindIndexHash,
]);
```

### Step 4: Query and Read Decrypted Records

[](#step-4-query-and-read-decrypted-records)

To retrieve records, search using the blind index hash and decrypt the returned envelope:

```
use Panda\Crypto\Envelope;

$searchQuery = '3201012501990001';

// 1. Hash the search value
$keyProvider = new LocalKeyProvider();
$blindIndexKey = $keyProvider->getKey("opd-dukcapil-key-1-idx");
$searchHash = BlindIndexer::generate($searchQuery, $blindIndexKey, 'digits');

// 2. Perform query lookup
$db = \Config\Database::connect();
$row = $db->table('citizens')
          ->where('nik_bidx', $searchHash)
          ->get()
          ->getRow();

if ($row) {
    $envelope = Envelope::fromJson($row->nik);

    // A. Masked value
    echo $cipher->decrypt($envelope); // Output: "32************01"

    // B. Raw unmasked value
    echo $cipher->decryptRaw($envelope); // Output: "3201012501990001"
}
```

---

5. Database Refactoring &amp; Zero-Downtime Migration Guide
-----------------------------------------------------------

[](#5-database-refactoring--zero-downtime-migration-guide)

If you have an existing application with a column containing plaintext data (e.g. `nik` VARCHAR(16) containing `'3201012501990001'`), follow this step-by-step workflow to migrate to encrypted and searchable storage without any system downtime.

### Step-by-Step Refactor Flow

[](#step-by-step-refactor-flow)

 ```
graph TD
    A[Step 1: Upgrade DB Column & Add Index Column] --> B[Step 2: Initialize PANDA Master Key]
    B --> C[Step 3: Run Chunked CLI Migration Command]
    C --> D[Step 4: Update Application Models/Logic]
    D --> E[Step 5: Run Backfill Script for Blind Indexes]
```

      Loading #### Step 1: Alter Database Column &amp; Add Index Column

[](#step-1-alter-database-column--add-index-column)

The existing plain-text column must be altered to `TEXT` (to fit the JSON envelope) and a separate index column (e.g. `_bidx`) must be added.

- **Laravel Migration:**```
    Schema::table('citizens', function (Blueprint $table) {
        $table->text('nik')->change();
        $table->string('nik_bidx', 64)->nullable()->index();
    });
    ```
- **CodeIgniter 4 / Vanilla SQL:**```
    ALTER TABLE citizens MODIFY COLUMN nik TEXT;
    ALTER TABLE citizens ADD COLUMN nik_bidx VARCHAR(64) NULL;
    CREATE INDEX idx_citizens_nik_bidx ON citizens(nik_bidx);
    ```

#### Step 2: Initialize PANDA Key

[](#step-2-initialize-panda-key)

Initialize your local master key (as detailed in Section 1) and make sure your server is restarted.

#### Step 3: Run Chunked Migration Command

[](#step-3-run-chunked-migration-command)

To convert all existing plaintext records in the database to encrypted envelopes, run the `migrate` CLI command:

```
# php vendor/bin/panda migrate
php vendor/bin/panda migrate citizens nik opd-dukcapil-key-1
```

This reads your DB in chunks (default: 100 rows), encrypts plaintext entries, updates them, and checkpoints its state in a `panda_migration_state` table so it can safely resume if interrupted.

#### Step 4: Update Application Code

[](#step-4-update-application-code)

- **Laravel:** Update the model with `HasEncryptedFields` and `EncryptedCast` (as shown in Section 3).
- **CodeIgniter / Vanilla:** Update CRUD operations to encrypt on write, compute blind indexes, and query using `where` on the `_bidx` column (as shown in Section 4).

#### Step 5: Backfill Blind Indexes for Existing Records

[](#step-5-backfill-blind-indexes-for-existing-records)

Since the CLI migration command only converts plaintext to encrypted envelopes, your database now has encrypted values but empty `_bidx` columns. You must backfill the blind indexes.

- **Laravel script:**```
    // Loop through rows missing the blind index and trigger a save (which automatically computes the bidx)
    foreach (Citizen::whereNull('nik_bidx')->cursor() as $citizen) {
        $citizen->save();
    }
    ```
- **CodeIgniter 4 / Vanilla PHP script:**```
    $db = \Config\Database::connect();
    $rows = $db->table('citizens')->where('nik_bidx', null)->get()->getResult();

    foreach ($rows as $row) {
        $envelope = Envelope::fromJson($row->nik);
        // Decrypt to get raw value
        $rawVal = $cipher->decryptRaw($envelope);

        // Calculate blind index
        $blindIndexHash = BlindIndexer::generate($rawVal, $blindIndexKey, 'digits');

        // Update
        $db->table('citizens')
           ->where('id', $row->id)
           ->update(['nik_bidx' => $blindIndexHash]);
    }
    ```

Once backfilled, your zero-downtime database migration is complete!

---

Security
--------

[](#security)

See [SECURITY.md](docs/security/SECURITY.md) for responsible disclosure.

License
-------

[](#license)

MIT

###  Health Score

38

—

LowBetter than 83% of packages

Maintenance97

Actively maintained with recent releases

Popularity6

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity37

Early-stage or recently created project

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~5 days

Total

7

Last Release

17d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/a76026a9c399be710cab27ffd3827cee1821cc77e8bf9ee67a76d488ddb1fabe?d=identicon)[jatimprovcsirt](/maintainers/jatimprovcsirt)

---

Top Contributors

[![garyjdn](https://avatars.githubusercontent.com/u/21191801?v=4)](https://github.com/garyjdn "garyjdn (12 commits)")

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/jatimprovcsirt-panda-php/health.svg)

```
[![Health](https://phpackages.com/badges/jatimprovcsirt-panda-php/health.svg)](https://phpackages.com/packages/jatimprovcsirt-panda-php)
```

###  Alternatives

[matomo/matomo

Matomo is the leading Free/Libre open analytics platform

21.8k40.0k](/packages/matomo-matomo)[laravel/framework

The Laravel Framework.

35.4k569.8M21.9k](/packages/laravel-framework)[composer/composer

Composer helps you declare, manage and install dependencies of PHP projects. It ensures you have the right stack everywhere.

29.6k203.2M3.5k](/packages/composer-composer)[drupal/core

Drupal is an open source content management platform powering millions of websites and applications.

21668.5M2.0k](/packages/drupal-core)[flow-php/flow

PHP ETL - Extract Transform Load - Data processing framework

86538.6k](/packages/flow-php-flow)[drupal/core-recommended

Locked core dependencies; require this project INSTEAD OF drupal/core.

7544.4M461](/packages/drupal-core-recommended)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
