option('path') ?? storage_path('app/backups');
$pretty = $this->option('pretty') ?? false;
$tablesFilter = $this->option('tables')
? array_map('trim', explode(',', $this->option('tables')))
: null;
if (! File::isDirectory($path)) {
File::makeDirectory($path, 0755, true);
}
$timestamp = now()->format('Y-m-d_His');
$backupDir = "{$path}/backup_{$timestamp}";
File::makeDirectory($backupDir, 0755, true);
$this->info("Backup directory: {$backupDir}");
$this->newLine();
$tables = $this->getTables();
if ($tablesFilter) {
$tables = array_filter($tables, fn ($t) => in_array($t, $tablesFilter));
}
$this->info("Exporting " . count($tables) . " tables...");
$this->newLine();
$bar = $this->output->createProgressBar(count($tables));
$bar->start();
$totalRows = 0;
foreach ($tables as $table) {
$count = $this->exportTable($table, $backupDir, $pretty);
$this->stats[$table] = $count;
$totalRows += $count;
$bar->advance();
}
$bar->finish();
$this->newLine(2);
// Write manifest
$manifest = [
'created_at' => now()->toIso8601String(),
'database' => config('database.default'),
'tables' => $this->stats,
'total_rows' => $totalRows,
];
File::put("{$backupDir}/manifest.json", json_encode($manifest, JSON_PRETTY_PRINT));
$this->printSummary($backupDir, $totalRows);
return self::SUCCESS;
}
private function getTables(): array
{
$driver = config('database.default');
if ($driver === 'sqlite') {
$results = DB::select("SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'");
return array_map(fn ($r) => $r->name, $results);
}
return DB::getDoctrineSchemaManager()->listTableNames();
}
private function exportTable(string $table, string $backupDir, bool $pretty): int
{
$filename = "{$backupDir}/{$table}.json";
$count = 0;
$handle = fopen($filename, 'w');
fwrite($handle, "[\n");
$first = true;
DB::table($table)->orderBy('id')->chunk(500, function ($rows) use ($handle, &$count, &$first, $pretty) {
foreach ($rows as $row) {
if (! $first) {
fwrite($handle, ",\n");
}
$first = false;
$data = (array) $row;
$json = $pretty
? json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)
: json_encode($data, JSON_UNESCAPED_UNICODE);
fwrite($handle, $json);
$count++;
}
});
fwrite($handle, "\n]\n");
fclose($handle);
return $count;
}
private function printSummary(string $backupDir, int $totalRows): void
{
$rows = [];
foreach ($this->stats as $table => $count) {
$rows[] = [$table, $count];
}
$rows[] = ['TOTAL', "{$totalRows}"];
$this->table(['Table', 'Rows'], $rows);
$size = File::size($backupDir);
$sizeFormatted = $this->formatBytes($size);
$this->info("Backup size: {$sizeFormatted}");
$this->info("Location: {$backupDir}");
}
private function formatBytes(int $bytes): string
{
$units = ['B', 'KB', 'MB', 'GB'];
$i = 0;
while ($bytes >= 1024 && $i < count($units) - 1) {
$bytes /= 1024;
$i++;
}
return round($bytes, 2) . ' ' . $units[$i];
}
}