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) { if ($stmt === '' || str_starts_with($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."); }