| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115 |
- <?php
- declare(strict_types=1);
- namespace App\Console\Commands;
- use Illuminate\Console\Command;
- use Illuminate\Support\Facades\Artisan;
- class DbBackupRotate extends Command
- {
- protected $signature = 'db:backup:rotate
- {--path= : Путь к каталогу с дампами (по умолчанию storage/db)}
- {--keep=7 : Сколько последних файлов дампа хранить}
- {--connection=mysql : Имя соединения из config/database.php}
- {--no-gzip : Не сжимать дамп gzip}';
- protected $description = 'Создаёт SQL-дамп базы данных и удаляет старые бэкапы, оставляя только последние N файлов';
- public function handle(): int
- {
- $keep = (int) $this->option('keep');
- if ($keep < 1) {
- $this->error('Опция --keep должна быть больше 0.');
- return self::FAILURE;
- }
- $path = $this->resolveBackupDirectory();
- $connection = (string) $this->option('connection');
- $exportOptions = [
- '--connection' => $connection,
- '--path' => $path,
- ];
- if ($this->option('no-gzip')) {
- $exportOptions['--no-gzip'] = true;
- }
- $this->info("Создаю ночной бэкап БД в {$path}...");
- $exitCode = Artisan::call('db:export', $exportOptions, $this->output);
- if ($exitCode !== self::SUCCESS) {
- $this->error('Бэкап не создан, ротация старых файлов пропущена.');
- return self::FAILURE;
- }
- $files = $this->findBackupFiles($path);
- if (count($files) <= $keep) {
- $this->info("Ротация не требуется. Файлов бэкапа: " . count($files));
- return self::SUCCESS;
- }
- $deleted = 0;
- foreach (array_slice($files, $keep) as $file) {
- if (@unlink($file)) {
- $deleted++;
- $this->line("Удалён старый бэкап: {$file}");
- } else {
- $this->warn("Не удалось удалить старый бэкап: {$file}");
- }
- }
- $this->info("Ротация завершена. Удалено файлов: {$deleted}. Оставлено: {$keep}.");
- return self::SUCCESS;
- }
- private function resolveBackupDirectory(): string
- {
- $path = $this->option('path');
- if (is_string($path) && $path !== '') {
- return $path;
- }
- return storage_path('db');
- }
- /**
- * @return list<string>
- */
- private function findBackupFiles(string $path): array
- {
- $files = glob(rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . '*');
- if ($files === false) {
- return [];
- }
- $backups = array_values(array_filter($files, function (string $file): bool {
- if (!is_file($file)) {
- return false;
- }
- return (bool) preg_match('/.+_\d{4}-\d{2}-\d{2}_\d{6}\.sql(?:\.gz)?$/', basename($file));
- }));
- usort($backups, static function (string $left, string $right): int {
- $mtimeCompare = filemtime($right) <=> filemtime($left);
- if ($mtimeCompare !== 0) {
- return $mtimeCompare;
- }
- return strcmp($right, $left);
- });
- return $backups;
- }
- }
|