%PDF- <> %âãÏÓ endobj 2 0 obj <> endobj 3 0 obj <>/ExtGState<>/ProcSet[/PDF/Text/ImageB/ImageC/ImageI] >>/Annots[ 28 0 R 29 0 R] /MediaBox[ 0 0 595.5 842.25] /Contents 4 0 R/Group<>/Tabs/S>> endobj ºaâÚÎΞ-ÌE1ÍØÄ÷{òò2ÿ ÛÖ^ÔÀá TÎ{¦?§®¥kuµùÕ5sLOšuY>endobj 2 0 obj<>endobj 2 0 obj<>endobj 2 0 obj<>endobj 2 0 obj<> endobj 2 0 obj<>endobj 2 0 obj<>es 3 0 R>> endobj 2 0 obj<> ox[ 0.000000 0.000000 609.600000 935.600000]/Fi endobj 3 0 obj<> endobj 7 1 obj<>/ProcSet[/PDF/Text/ImageB/ImageC/ImageI]>>/Subtype/Form>> stream
<?php
/**
* migrate.php — Penjalan migrasi skema database (PHP native, tanpa framework).
* PPID Kabupaten Mamuju Tengah.
*
* Menjalankan berkas *.sql di folder migrations/ yang BELUM pernah dijalankan,
* URUT berdasarkan nama berkas (prefiks timestamp). Tiap migrasi yang sukses
* dicatat di tabel `schema_migrations`, sehingga perintah aman dijalankan
* berulang (yang sudah jalan dilewati).
*
* HANYA CLI — menolak akses lewat browser.
*
* PEMAKAIAN:
* php scripts/migrate.php Jalankan semua migrasi yang tertunda.
* php scripts/migrate.php status Tampilkan status (sudah / tertunda).
* php scripts/migrate.php make "judul" Buat berkas migrasi baru (prefiks timestamp).
*/
// ── Hanya boleh dari terminal (CLI), tidak dari web ──
if (PHP_SAPI !== 'cli') {
http_response_code(403);
exit('Akses ditolak. Skrip ini hanya untuk terminal (CLI).');
}
define('APP_ENV', 'development'); // agar pesan error DB tampil saat migrasi
$root = dirname(__DIR__);
$migrationsDir = $root . '/migrations';
$configDb = $root . '/config/database.php';
if (!is_file($configDb)) {
fwrite(STDERR, "GAGAL: config/database.php belum ada. Salin dari config/database.example.php dulu.\n");
exit(1);
}
require $configDb; // menyediakan konstanta DB_* dan fungsi db()
if (!is_dir($migrationsDir)) {
mkdir($migrationsDir, 0775, true);
}
/**
* Pisahkan isi berkas SQL menjadi pernyataan-pernyataan tunggal.
* Menangani komentar (-- , #, blok), string ('...', "..."), identifier (`...`),
* dan escape backslash di dalam string. Tidak menangani DELIMITER kustom
* (jangan pakai trigger/stored procedure di migrasi — cukup DDL/DML biasa).
*
* @return string[]
*/
function split_sql_statements(string $sql): array
{
$out = [];
$buf = '';
$n = strlen($sql);
$inS = $inD = $inB = false; // single, double, backtick
$lineC = $blockC = false; // komentar baris / blok
for ($i = 0; $i < $n; $i++) {
$c = $sql[$i];
$next = $i + 1 < $n ? $sql[$i + 1] : '';
if ($lineC) {
if ($c === "\n") { $lineC = false; $buf .= $c; }
continue;
}
if ($blockC) {
if ($c === '*' && $next === '/') { $blockC = false; $i++; }
continue;
}
// Escape backslash di dalam string → salin dua karakter sekaligus.
if (($inS || $inD) && $c === '\\') {
$buf .= $c . $next;
$i++;
continue;
}
if (!$inS && !$inD && !$inB) {
if (($c === '-' && $next === '-') || $c === '#') { $lineC = true; continue; }
if ($c === '/' && $next === '*') { $blockC = true; $i++; continue; }
}
if ($c === "'" && !$inD && !$inB) { $inS = !$inS; }
elseif ($c === '"' && !$inS && !$inB) { $inD = !$inD; }
elseif ($c === '`' && !$inS && !$inD) { $inB = !$inB; }
if ($c === ';' && !$inS && !$inD && !$inB) {
$stmt = trim($buf);
if ($stmt !== '') { $out[] = $stmt; }
$buf = '';
continue;
}
$buf .= $c;
}
$tail = trim($buf);
if ($tail !== '') { $out[] = $tail; }
return $out;
}
/** Pastikan tabel pencatat migrasi ada. */
function ensure_migrations_table(PDO $pdo): void
{
$pdo->exec(
'CREATE TABLE IF NOT EXISTS `schema_migrations` ('
. '`id` INT AUTO_INCREMENT PRIMARY KEY,'
. '`migration` VARCHAR(255) NOT NULL UNIQUE,'
. '`executed_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP'
. ') ENGINE=InnoDB DEFAULT CHARSET=utf8mb4'
);
}
/** Daftar berkas migrasi (*.sql) urut nama. @return string[] */
function migration_files(string $dir): array
{
$files = glob($dir . '/*.sql') ?: [];
$files = array_map('basename', $files);
sort($files, SORT_STRING);
return $files;
}
/** Migrasi yang sudah tercatat. @return array<string,string> nama => waktu */
function ran_migrations(PDO $pdo): array
{
$rows = $pdo->query('SELECT migration, executed_at FROM schema_migrations ORDER BY migration')->fetchAll(PDO::FETCH_ASSOC);
$map = [];
foreach ($rows as $r) { $map[$r['migration']] = $r['executed_at']; }
return $map;
}
// ─────────────────────────────────────────────────────────────────────────────
// Perintah
// ─────────────────────────────────────────────────────────────────────────────
$cmd = $argv[1] ?? 'run';
if ($cmd === 'make') {
$desc = trim($argv[2] ?? '');
if ($desc === '') {
fwrite(STDERR, "Pemakaian: php scripts/migrate.php make \"deskripsi singkat\"\n");
exit(1);
}
$slug = strtolower(preg_replace('/[^a-zA-Z0-9]+/', '_', $desc));
$slug = trim($slug, '_') ?: 'migrasi';
$name = date('YmdHis') . '_' . $slug . '.sql';
$path = $migrationsDir . '/' . $name;
$tpl = "-- Migrasi: {$desc}\n"
. '-- Dibuat : ' . date('Y-m-d H:i:s') . "\n"
. "--\n"
. "-- Tulis pernyataan SQL di bawah; pisahkan tiap pernyataan dengan ';'.\n"
. "-- Migrasi yang sukses dicatat & tidak dijalankan ulang.\n"
. "-- Contoh:\n"
. "-- ALTER TABLE `upd` ADD COLUMN `telepon` VARCHAR(30) NULL AFTER `email`;\n"
. "-- CREATE TABLE IF NOT EXISTS `contoh` ( `id` INT AUTO_INCREMENT PRIMARY KEY );\n\n";
file_put_contents($path, $tpl);
echo "Berkas migrasi dibuat:\n migrations/{$name}\n";
exit(0);
}
$pdo = db();
ensure_migrations_table($pdo);
$files = migration_files($migrationsDir);
$ran = ran_migrations($pdo);
if ($cmd === 'status') {
echo "Status migrasi:\n";
if (!$files) { echo " (belum ada berkas migrasi di migrations/)\n"; exit(0); }
foreach ($files as $f) {
$mark = isset($ran[$f]) ? '[x] sudah ' . $ran[$f] : '[ ] TERTUNDA';
printf(" %s %s\n", $mark, $f);
}
$pending = count(array_diff($files, array_keys($ran)));
echo "\n Total: " . count($files) . " migrasi, {$pending} tertunda.\n";
exit(0);
}
// cmd === 'run' (default): jalankan yang tertunda
$pending = array_values(array_diff($files, array_keys($ran)));
if (!$pending) {
echo "Tidak ada migrasi tertunda. Database sudah paling baru.\n";
exit(0);
}
echo count($pending) . " migrasi tertunda. Menjalankan...\n\n";
$insert = $pdo->prepare('INSERT INTO schema_migrations (migration) VALUES (?)');
foreach ($pending as $file) {
echo "→ {$file}\n";
$sql = (string) file_get_contents($migrationsDir . '/' . $file);
$stmts = split_sql_statements($sql);
try {
foreach ($stmts as $idx => $stmt) {
$pdo->exec($stmt);
}
$insert->execute([$file]);
echo " ✓ sukses (" . count($stmts) . " pernyataan)\n\n";
} catch (Throwable $e) {
echo " ✗ GAGAL: " . $e->getMessage() . "\n";
fwrite(STDERR, "\nMigrasi DIHENTIKAN pada {$file}. Perbaiki lalu jalankan lagi.\n");
fwrite(STDERR, "Catatan: pernyataan DDL MySQL (ALTER/CREATE) auto-commit, jadi\n");
fwrite(STDERR, "sebagian mungkin sudah diterapkan. Periksa kondisi tabel sebelum ulang.\n");
exit(1);
}
}
echo "Selesai. Semua migrasi tertunda berhasil dijalankan.\n";