exec('CREATE TABLE IF NOT EXISTS _migrations (name TEXT PRIMARY KEY, applied_at TEXT NOT NULL);'); $files = glob(rtrim($migrationsDir, '/') . '/*.sql') ?: []; sort($files); foreach ($files as $file) { $name = basename($file); $stmt = $db->prepare('SELECT 1 FROM _migrations WHERE name = :n'); $stmt->execute([':n' => $name]); if ($stmt->fetchColumn()) { continue; } $sql = file_get_contents($file); if ($sql === false) { throw new RuntimeException("Cannot read migration: $name"); } // Les toutes premières installations utilisaient déjà deleted_at avant // que cette colonne ne soit correctement inscrite dans une migration. if ($name === '025_animal_archive_and_trash.sql') { $columns = array_column($db->query('PRAGMA table_info(animals)')->fetchAll(PDO::FETCH_ASSOC), 'name'); if (!in_array('deleted_at', $columns, true)) { $db->exec('ALTER TABLE animals ADD COLUMN deleted_at TEXT'); } } // Répare les bases créées par une candidate rc.3 incomplète, dans // laquelle les médias privés étaient utilisés avant d'être migrés. if ($name === '089_media_schema_repair.sql') { $columns = array_column( $db->query('PRAGMA table_info(animal_photos)')->fetchAll(PDO::FETCH_ASSOC), 'name', ); if (!in_array('is_public', $columns, true)) { $db->exec('ALTER TABLE animal_photos ADD COLUMN is_public INTEGER NOT NULL DEFAULT 1'); } } $db->beginTransaction(); try { $db->exec($sql); $ins = $db->prepare("INSERT INTO _migrations(name, applied_at) VALUES(:n, datetime('now'))"); $ins->execute([':n' => $name]); $db->commit(); } catch (Throwable $e) { $db->rollBack(); throw new RuntimeException('Migration ' . $name . ' : ' . $e->getMessage(), 0, $e); } } } }