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 */ 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; } }