Инструменты PHP для восстановления файлов из архивов и директорий

Раздел: Администрирование PHP -> Резервное копирование и восстановление

Основные подходы к восстановлению файлов

Эффективное восстановление из ZIP-архива

Как восстановить все файлы проекта из zip-архива, созданного резервной копией?

Этот метод использует встроенное расширение ZipArchive. Скрипт открывает архив, извлекает его содержимое в целевую директорию и закрывает архив. Перед использованием необходимо убедиться в наличии расширения (extension=zip) и прав на запись.

$zip = new ZipArchive();
if ($zip->open('/backup/site_backup.zip') === TRUE) {
    $zip->extractTo('/var/www/html/');
    $zip->close();
    echo 'Восстановление завершено.';
} else {
    echo 'Ошибка открытия архива.';
}

файл restore php (восстановление файлов в php)

Пояснение шагов: Параметр open принимает путь к архиву. extractTo указывает целевую папку. Если папка не существует, она не создается автоматически. После успешного извлечения архив закрывается. Для больших архивов используйте set_time_limit(0).

Типичные ошибки: расширение ZipArchive не загружено (проверьте php.ini); недостаточно прав на запись в целевую директорию; путь к архиву неверен; при большом количестве мелких файлов возможен переполнение памяти. Решение: установка временного лимита и использование статичных методов извлечения с callable-обработчиком для пофайлового контроля.

Цель метода - быстрое полное восстановление содержимого архива. Случаи использования: восстановление после сбоя сервера, разворачивание проекта на новом хостинге, перенос данных между серверами.

Как восстановить файлы из простой копии директории?

Если резервная копия представляет собой набор файлов и папок без архивации, применяют рекурсивное копирование. Данный подход прост, но неэффективен для глубоких деревьев.

function copyRecursive($src, $dst) {
    $dir = opendir($src);
    if (!is_dir($dst)) {
        mkdir($dst, 0755, true);
    }
    while (($file = readdir($dir)) !== false) {
        if ($file != '.' && $file != '..') {
            $srcFile = "$src/$file";
            $dstFile = "$dst/$file";
            if (is_dir($srcFile)) {
                copyRecursive($srcFile, $dstFile);
            } else {
                copy($srcFile, $dstFile);
            }
        }
    }
    closedir($dir);
}
copyRecursive('/backup/files', '/var/www/html');

Здесь вручную открывается директория, и для каждого элемента создается целевая папка или копируется файл. Альтернатива - использование RecursiveDirectoryIterator.

Проблемы: при глубокой вложенности возможен переполнение стека рекурсии; не переносятся права доступа и владельцы; большая нагрузка на диск при множестве файлов. Решение: увеличение лимита вложенности с помощью xdebug.max_nesting_level или переход на итераторы.

Цель - быстрое восстановление структуры без установки дополнительных расширений. Случаи использования: мелкие проекты, тестовые среды, миграция между локальными папками.

Как восстановить файлы через FTP при отсутствии прямого доступа к файловой системе?

Если сервер не имеет прямого доступа к хранилищу резервных копий (например, внешний хост), используется FTP-соединение. PHP предоставляет набор ftp-функций для работы с удаленной файловой системой.

$ftp = ftp_connect('backup.example.com');
ftp_login($ftp, 'user', 'pass');
ftp_pasv($ftp, true);

$localPath = '/var/www/html';
$remotePath = '/backup/files';

// рекурсивная загрузка всех файлов
$files = ftp_nlist($ftp, $remotePath);
if ($files) {
    foreach ($files as $remoteFile) {
        $localFile = $localPath . '/' . basename($remoteFile);
        ftp_get($ftp, $localFile, $remoteFile, FTP_BINARY);
    }
}
ftp_close($ftp);

Пояснение: сначала устанавливается пассивный режим (FTP_PASV). С помощью ftp_nlist получаем список файлов, затем загружаем каждый. Для рекурсивного обхода папок требуется собственная рекурсивная функция.

Типичные ошибки: блокировка портов на сервере (21, 20); необходимость SSL (ftps://); падение соединения при большом количестве файлов; разные права доступа. Рекомендуется использовать ftp_ssl_connect при подключении по FTPS и таймаут на соединение.

Цель - восстановление данных с удаленного хранилища без монтирования сетевой папки. Случаи использования: облачные бэкапы, внешние FTP-серверы, хостинг с ограниченным доступом к shell.

Как восстановить файлы из tar-архивов с помощью Phar?

Для поддержки tar, tar.gz, tar.bz2 используется класс PharData (расширение Phar). Метод extractTo извлекает весь архив в указанную папку.

$phar = new PharData('/backup/site.tar.gz');
$phar->extractTo('/var/www/html/', null, true);
echo 'Файлы извлечены из tar.gz';

Третий параметр (overwrite) - перезаписывать ли существующие файлы. Если требуется извлечь только отдельные файлы, вторым параметром передается массив путей.

Проблемы: расширение Phar может быть отключено в php.ini; нет автоматического создания целевой директории; при извлечении большого количества файлов возможен рост памяти. Решение: предварительная проверка существования папки, использование phar.readonly = Off в php.ini.

Цель - работа с распространенными форматами архивации без внешних утилит. Случаи использования: Linux-серверы, где tar является стандартом; бэкапы, снятые через cron.

Как восстановить файлы через shell rsync для синхронизации целых папок?

Если на сервере разрешены функции exec или shell_exec, можно выполнить системную команду rsync. Это эффективно для больших объемов данных.

$backupDir = '/backup/site/';   // источник (локальный)
$targetDir = '/var/www/html/';  // назначение
$command = "rsync -av --delete $backupDir $targetDir";
$output = shell_exec($command);
echo "
$output
";

Флаг -av - архивирование и подробный вывод, --delete - удаление файлов в целевой папке, которых нет в источнике.

Ошибки: rsync может быть не установлен; недостаточно прав для выполнения shell-команд в php.ini (disable_functions); опасность внедрения команд. Всегда экранируйте пути с помощью escapeshellarg.

Цель - синхронизация целых деревьев каталогов с минимальным трафиком. Случаи использования: восстановление на локальной машине, зеркалирование, инкрементальные бэкапы.

Как восстановить отдельные файлы из базы данных (BLOB-хранение)?

Иногда файлы сохраняются в таблицах MySQL в виде BLOB. Тогда восстановление происходит через SQL-запросы с последующей записью на диск.

$pdo = new PDO('mysql:host=localhost;dbname=backup', 'user', 'pass');
$stmt = $pdo->query('SELECT filename, filedata FROM file_backup WHERE id = 1');
$row = $stmt->fetch(PDO::FETCH_ASSOC);
file_put_contents('/var/www/html/' . $row['filename'], $row['filedata']);

Обратите внимание: BLOB может быть очень большим, поэтому используйте PDO::PARAM_LOB и потоковую запись.

Проблемы: переполнение памяти при чтении большого BLOB; время выполнения запроса; кодировка, если файл текстовый. Решение: загрузка частями через stream.

Цель - восстановление единичных файлов, если резервное копирование велось в СУБД. Случаи использования: CMS с хранением изображений в базе, мелкие проекты.

Проверка целостности восстановленных файлов по контрольным суммам

После восстановления полезно сверить хеши файлов с эталонными, чтобы убедиться в отсутствии ошибок.

Пример
$hashesFile = '/backup/hashes.json';
if (!file_exists($hashesFile)) {
    echo 'Файл с хешами не найден.';
    exit;
}
$hashes = json_decode(file_get_contents($hashesFile), true);
$errors = [];
foreach ($hashes as $relativePath => $expectedHash) {
    $fullPath = '/var/www/html/' . ltrim($relativePath, '/');
    if (file_exists($fullPath)) {
        $actualHash = md5_file($fullPath);
        if ($actualHash !== $expectedHash) {
            $errors[] = "Не совпадает хеш для $relativePath (ожидалось $expectedHash, получено $actualHash)";
        }
    } else {
        $errors[] = "Файл $relativePath отсутствует";
    }
}
if (empty($errors)) {
    echo 'Все файлы прошли проверку целостности.';
} else {
    echo 'Обнаружены ошибки:
' . implode('
', $errors); }
Все файлы прошли проверку целостности.

Восстановление с логом операций и прерыванием при ошибке

Для крупных восстановлений полезно вести лог и останавливаться при первой ошибке, чтобы не копить неверные данные.

Пример
$logFile = '/var/log/restore.log';
$logHandle = fopen($logFile, 'a');
$fail = false;

$zip = new ZipArchive();
if ($zip->open('/backup/site.zip') === TRUE) {
    for ($i = 0; $i < $zip->numFiles; $i++) {
        $filename = $zip->getNameIndex($i);
        if ($zip->extractTo('/var/www/html/', $filename) === false) {
            fwrite($logHandle, "[ERROR] Не удалось извлечь $filename\n");
            $fail = true;
            break; // прерываем
        }
        fwrite($logHandle, "[OK] $filename\n");
    }
    $zip->close();
} else {
    fwrite($logHandle, "[FATAL] Не удалось открыть архив\n");
    $fail = true;
}
fclose($logHandle);
if ($fail) {
    echo 'Восстановление прервано из-за ошибок. Подробности в логе.';
} else {
    echo 'Восстановление успешно, лог сохранен.';
}
Восстановление прервано из-за ошибок. Подробности в логе.

Восстановление с переименованием конфликтующих файлов (добавление временной метки)

Если требуется сохранить существующие файлы, а не перезаписывать их, можно добавить к имени метку времени.

Пример
$zip = new ZipArchive();
$targetDir = '/var/www/html/';
if ($zip->open('/backup/site.zip') === TRUE) {
    for ($i = 0; $i < $zip->numFiles; $i++) {
        $filename = $zip->getNameIndex($i);
        $targetPath = $targetDir . $filename;
        if (file_exists($targetPath)) {
            $info = pathinfo($targetPath);
            $newName = $info['filename'] . '_' . date('YmdHis') . '.' . ($info['extension'] ?? '');
            $targetPath = $info['dirname'] . '/' . $newName;
        }
        $zip->extractTo($targetDir, $filename);
        if ($targetPath !== $targetDir . $filename) {
            rename($targetDir . $filename, $targetPath);
        }
    }
    $zip->close();
    echo 'Файлы восстановлены, конфликты переименованы.';
} else {
    echo 'Ошибка открытия архива.';
}
Файлы восстановлены, конфликты переименованы.

Потоковая загрузка больших файлов из FTP с возобновлением

Для файлов размером более 2 ГБ или при нестабильном соединении используется ftp_nb_get (non-blocking).

Пример
$ftp = ftp_connect('backup.example.com');
ftp_login($ftp, 'user', 'pass');
ftp_pasv($ftp, true);

$remoteFile = '/backups/large.sql.gz';
$localFile = '/var/www/html/large.sql.gz';
$ret = ftp_nb_get($ftp, $localFile, $remoteFile, FTP_BINARY);
while ($ret == FTP_MOREDATA) {
    echo '.';  // индикация прогресса
    $ret = ftp_nb_continue($ftp);
}
if ($ret == FTP_FINISHED) {
    echo "\nСкачивание завершено.";
} else {
    echo "\nОшибка скачивания.";
}
ftp_close($ftp);
..........
Скачивание завершено.

Восстановление с использованием SCP через proc_open

SCP - безопасный способ передачи файлов по SSH. В PHP можно запустить scp через proc_open.

Пример
$descriptorspec = [
    0 => ['pipe', 'r'],  // stdin
    1 => ['pipe', 'w'],  // stdout
    2 => ['pipe', 'w']   // stderr
];
$process = proc_open(
    'scp -r user@backup.server:/backup/site/* /var/www/html/',
    $descriptorspec,
    $pipes
);
if (is_resource($process)) {
    fclose($pipes[0]);
    $output = stream_get_contents($pipes[1]);
    $error = stream_get_contents($pipes[2]);
    fclose($pipes[1]);
    fclose($pipes[2]);
    $return_value = proc_close($process);
    if ($return_value === 0) {
        echo 'Восстановление через SCP выполнено.';
    } else {
        echo 'Ошибка SCP: ' . $error;
    }
}
Восстановление через SCP выполнено.

Восстановление файлов в PHP - comments

En
файл restore php (php)