| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137 |
- <?php
- declare(strict_types=1);
- namespace App\Console\Commands;
- use Illuminate\Console\Command;
- use Symfony\Component\Process\ExecutableFinder;
- use Symfony\Component\Process\Process;
- class DbExport extends Command
- {
- protected $signature = 'db:export
- {--path= : Путь для сохранения файла (по умолчанию storage/db)}
- {--no-gzip : Не сжимать дамп gzip}
- {--connection=mysql : Имя соединения из config/database.php}';
- protected $description = 'Полный SQL-дамп базы данных через mysqldump';
- public function handle(): int
- {
- $dumpBinary = $this->resolveExecutable(['mysqldump', 'mariadb-dump']);
- if ($dumpBinary === null) {
- $this->error('Не найден mysqldump/mariadb-dump. Установите mysql-client в окружение, где запускается команда.');
- return self::FAILURE;
- }
- $connection = $this->option('connection');
- $config = config("database.connections.{$connection}");
- if (!$config || !in_array($config['driver'] ?? null, ['mysql', 'mariadb'], true)) {
- $this->error("Соединение {$connection} не найдено или не MySQL/MariaDB.");
- return self::FAILURE;
- }
- $database = $config['database'];
- $host = $config['host'] ?? '127.0.0.1';
- $port = (string) ($config['port'] ?? '3306');
- $user = $config['username'] ?? 'root';
- $password = (string) ($config['password'] ?? '');
- $sslOptions = $this->resolveCliSslOptions($config);
- $gzip = !$this->option('no-gzip');
- $timestamp = date('Y-m-d_His');
- $filename = "{$database}_{$timestamp}.sql" . ($gzip ? '.gz' : '');
- $dir = $this->resolveBackupDirectory();
- if (!is_dir($dir) && !mkdir($dir, 0775, true) && !is_dir($dir)) {
- $this->error("Не удалось создать каталог: {$dir}");
- return self::FAILURE;
- }
- $filepath = rtrim($dir, '/') . '/' . $filename;
- $cmd = sprintf(
- '%s --host=%s --port=%s --user=%s %s %s --single-transaction --quick --routines --triggers --events --hex-blob --default-character-set=utf8mb4 %s',
- escapeshellarg($dumpBinary),
- escapeshellarg($host),
- escapeshellarg($port),
- escapeshellarg($user),
- $password !== '' ? '--password=' . escapeshellarg($password) : '',
- $sslOptions,
- escapeshellarg($database),
- );
- if ($gzip) {
- $cmd .= ' | gzip';
- }
- $cmd .= ' > ' . escapeshellarg($filepath);
- $this->info("Создаю дамп БД {$database} → {$filepath}");
- $process = new Process(['bash', '-o', 'pipefail', '-c', $cmd]);
- $process->setTimeout(null);
- $process->run(function ($type, $buffer) {
- if ($type === Process::ERR) {
- $this->getOutput()->write($buffer);
- }
- });
- if (!$process->isSuccessful()) {
- @unlink($filepath);
- $this->error('Ошибка mysqldump: ' . $process->getErrorOutput());
- return self::FAILURE;
- }
- $size = is_file($filepath) ? filesize($filepath) : 0;
- $this->info(sprintf('Готово. Размер: %.2f MB', $size / 1024 / 1024));
- $this->line($filepath);
- return self::SUCCESS;
- }
- private function resolveBackupDirectory(): string
- {
- $path = $this->option('path');
- if (is_string($path) && $path !== '') {
- return $path;
- }
- return storage_path('db');
- }
- /**
- * @param array<string, mixed> $config
- */
- private function resolveCliSslOptions(array $config): string
- {
- $sslCa = $config['options'][\PDO::MYSQL_ATTR_SSL_CA] ?? null;
- if (is_string($sslCa) && $sslCa !== '') {
- return '--ssl-ca=' . escapeshellarg($sslCa);
- }
- return '--skip-ssl';
- }
- /**
- * @param list<string> $candidates
- */
- private function resolveExecutable(array $candidates): ?string
- {
- $finder = new ExecutableFinder();
- foreach ($candidates as $candidate) {
- $binary = $finder->find($candidate);
- if ($binary !== null) {
- return $binary;
- }
- }
- return null;
- }
- }
|