Библиотека для резервного копирования пользовательских данных из нескольких баз данных с потоковой записью, чанковым шифрованием и опциональным удалением исходных данных.
Документация:
- Потоковое чтение данных из БД без загрузки всего набора в память.
- Потоковая запись backup JSON в файл.
- Чанковое шифрование больших backup-файлов.
- Потоковое чтение backup-файлов через
streamBackupData(). - Объектный сценарий создания backup через
UserBackupCreateOptions. - Ручной сценарий создания backup-сервиса.
- Контейнерный сценарий через factory.
- Объектный сценарий очистки через
UserDataScope. - Очистка пользовательских данных после backup.
- Keyset-пагинация (
lazyById) по числовому первичному ключу — не деградирует с глубиной; для составных/нечисловых PK автоматический OFFSET-fallback. - Подзапрос-фильтр через
FilterSubquery— заменаwhereIn([десятки тысяч литералов])на компактныйIN (SELECT ...). - Предзагрузка схемы одним запросом на подключение (таблицы/колонки/PK) — снимает per-table round-trip «налог» при обходе всех таблиц удалённой БД.
- Пустые таблицы не попадают в файл бэкапа (нечего восстанавливать) — меньше размер и быстрее restore.
- Пакет ориентирован в первую очередь на MySQL-подобный сценарий.
- Generic restore внутри пакета сейчас не реализован.
- Raw rows таблиц остаются массивами, потому что схема таблиц динамическая.
- Runtime-параметры backup use case не должны резолвиться напрямую из контейнера без явного scope.
composer require fin/user-backup-libPackagist:
use App\Services\UserBackupService;
use App\ValueObjects\UserBackupCreateOptions;
$options = UserBackupCreateOptions::fromLegacy(
userId: 42,
accountIds: [101, 102],
activeIds: [501],
ignoredTables: ['temp_logs'],
connections: ['mysql', 'replica'],
);
$backup = UserBackupService::createFromOptions($options);Legacy-совместимый вариант:
use App\Services\UserBackupService;
$backup = UserBackupService::create(
userId: 42,
accountIds: [101, 102],
activeIds: [501],
ignoredTables: ['temp_logs'],
connections: ['mysql', 'replica'],
);
$backup->fetchAllUserData();
$path = $backup->saveBackupToFile('/tmp/backup_42.json');
// вернется /tmp/backup_42.json.encКогда набор значений фильтра огромен (например, десятки тысяч active_id), передача их
литеральным списком делает SQL многокилобайтным и дорогим в разборе на каждой таблице.
Вместо этого можно передать спецификацию подзапроса — библиотека подставит
active_id IN (SELECT id FROM actives WHERE user_id = ?). Подзапрос применяется только
если целевая таблица (actives) присутствует в подключении; иначе — обычный whereIn.
use App\Services\UserBackupService;
use App\ValueObjects\FilterSubquery;
$backup = UserBackupService::create(
userId: 42,
accountIds: [101, 102],
activeIds: [501, 502], // используется как fallback, если подзапрос неприменим
ignoredTables: ['temp_logs'],
connections: ['mysql', 'replica'],
activeIdSubquery: new FilterSubquery(
table: 'actives', // откуда брать id
selectColumn: 'id',
whereColumn: 'user_id',
whereValue: 42,
),
);
$backup->fetchAllUserData();
$path = $backup->saveBackupToFile('/tmp/backup_42.json');Важно: подзапрос обязан воспроизводить тот же набор строк, что и литеральный список (те же soft-delete/scope-условия), иначе в backup попадут «лишние» строки.
use App\Contracts\UserBackupServiceFactoryInterface;
use App\ValueObjects\UserDataScope;
$factory = app(UserBackupServiceFactoryInterface::class);
$backup = $factory->make(new UserDataScope(
userId: 42,
accountIds: [101, 102],
activeIds: [501],
ignoredTables: ['temp_logs'],
));
$backup->fetchAllUserData();
$path = $backup->saveBackupToFile(storage_path('app/backups/user-42.json'));Legacy-совместимый вариант:
$backup = $factory->makeForUser(
userId: 42,
accountIds: [101, 102],
activeIds: [501],
ignoredTables: ['temp_logs'],
);use App\Contracts\UserDataDeletionServiceInterface;
use App\ValueObjects\UserDataScope;
$cleaner = app(UserDataDeletionServiceInterface::class);
$cleaner->deleteScope(new UserDataScope(
userId: 42,
accountIds: [101, 102],
activeIds: [501],
ignoredTables: ['temp_logs'],
));Legacy-совместимый вариант:
$cleaner->deleteUserData(
userId: 42,
accountIds: [101, 102],
activeIds: [501],
ignoredTables: ['temp_logs'],
);use App\Services\FileStorageService;
$data = FileStorageService::decryptFile('/tmp/backup_42.json.enc');use App\Services\FileStorageService;
$storage = new FileStorageService();
foreach ($storage->streamBackupData('/tmp/backup_42.json.enc') as $entry) {
$table = $entry['table'];
$row = $entry['row'];
}Совместимость форматов:
.jsonчитается потоково.- новый чанковый
.json.encчитается потоково. - legacy
.encподдерживается по совместимости, но не гарантирует ту же memory-efficiency на очень больших файлах.
Пакетный конфиг: config/user-backup.php
return [
'connections' => ['mysql', 'replica'],
];Правила:
- если
user-backup.connectionsпуст, пакет берет все ключи изdatabase.connections; - для шифрования используется стандартный
APP_KEYLaravel; - путь сохранения backup выбирает приложение, не пакет.
- параметр
accountIdsв текущей интеграции проекта исторический и по факту содержит ids субсчетов.
php -d pcov.enabled=1 vendor/bin/phpunit --coverage-text --coverage-html build/coverageТекущее покрытие пакета: 100%.
src/Contracts/*— контракты сервисов и factory.src/Services/DatabaseService.php— потоковое чтение из БД.src/Services/FileStorageService.php— запись, шифрование и чтение backup-файлов.src/Services/UserBackupService.php— orchestration backup use case.src/Services/UserBackupServiceFactory.php— factory для container-friendly сценария.src/Services/UserDataDeletionService.php— очистка данных.src/ValueObjects/*— внутренние DTO/value objects.docs/user-backup-guide.md— полная документация.