PDO::ERRMODE_EXCEPTION, PDO::ATTR_EMULATE_PREPARES => false, ] ); out("Connexion DB OK"); } catch (Exception $e) { out("ÉCHEC connexion : " . $e->getMessage()); exit(2); } $migrationsDir = dirname(__DIR__) . '/migrations'; $files = glob("$migrationsDir/*.sql") ?: []; sort($files); if (!$files) { out("Aucun fichier SQL dans $migrationsDir"); exit(0); } foreach ($files as $f) { out(""); out("--- " . basename($f) . " ---"); $sql = file_get_contents($f); // Découpe naïve sur les ';' suivis d'un saut de ligne (suffit pour notre schéma) $statements = array_filter(array_map('trim', preg_split('/;\s*\n/', $sql) ?: [])); foreach ($statements as $stmt) { // Strip les lignes de commentaire SQL (--) du début / intercalées avant // de checker si le statement est vide. Sinon : un fichier migration qui // commence par un header de commentaires se retrouve dans le PREMIER // chunk avec son premier ALTER (séparés par \n pas par ;\n) ; le chunk // commence donc par `--`, le check str_starts_with le déclare vide, // et l'ALTER est silencieusement skip. Bug rapporté : migrations 003+ // qui n'ont qu'un seul ALTER après un bloc de commentaires en tête // n'étaient jamais appliquées (output vide entre "--- 003 ---" et "==="). $lines = preg_split('/\r?\n/', $stmt) ?: []; $kept = []; foreach ($lines as $line) { $t = ltrim($line); if ($t === '' || str_starts_with($t, '--')) continue; $kept[] = $line; } $stmt = trim(implode("\n", $kept)); if ($stmt === '') continue; try { $pdo->exec($stmt); $first40 = substr(preg_replace('/\s+/', ' ', $stmt), 0, 60); out(" ✔ " . $first40 . "…"); } catch (Exception $e) { $msg = $e->getMessage(); // Tolérance idempotence : on n'aboie pas sur les ALTER qui re-touchent // une colonne / un index existant. Permet de rejouer migrate.php sans // risque sur une DB déjà partiellement / complètement migrée. $idempotent = str_contains($msg, 'Duplicate column') || str_contains($msg, 'Duplicate key name') || str_contains($msg, "Can't DROP") // ALTER DROP COLUMN inexistante || str_contains($msg, 'check that column/key exists'); if ($idempotent) { $first40 = substr(preg_replace('/\s+/', ' ', $stmt), 0, 60); out(" ↺ (déjà appliqué) " . $first40 . "…"); continue; } out(" ✘ ERREUR : " . $msg); out(" Sur : " . substr($stmt, 0, 80) . "…"); exit(3); } } } out(""); out("=== Migration terminée. Tables présentes ==="); $tables = $pdo->query("SHOW TABLES")->fetchAll(PDO::FETCH_COLUMN); foreach ($tables as $t) out(" - $t"); if (!$cli) { out(""); out("⚠ Pour la sécurité, supprime maintenant tools/migrate.php du serveur."); }