| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150 |
- <?php
- declare(strict_types=1);
- namespace App\Console\Commands;
- use Illuminate\Console\Command;
- use Symfony\Component\Process\ExecutableFinder;
- use Symfony\Component\Process\Process;
- class DbImport extends Command
- {
- protected $signature = 'db:import
- {file : Путь к .sql или .sql.gz файлу или имя файла из storage/db}
- {--connection=mysql : Имя соединения из config/database.php}
- {--force : Не запрашивать подтверждение}
- {--drop : Очистить БД (DROP+CREATE) перед импортом}';
- protected $description = 'Импорт SQL-дампа в базу данных через mysql';
- public function handle(): int
- {
- $mysqlBinary = $this->resolveExecutable(['mysql', 'mariadb']);
- if ($mysqlBinary === null) {
- $this->error('Не найден mysql/mariadb client. Установите mysql-client в окружение, где запускается команда.');
- return self::FAILURE;
- }
- $file = $this->resolveImportFile((string) $this->argument('file'));
- if (!is_file($file)) {
- $this->error("Файл не найден: {$file}");
- 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);
- if (!$this->option('force') && !$this->confirm("Импортировать дамп в БД {$database}? Текущие данные будут перезаписаны.", false)) {
- $this->warn('Отменено.');
- return self::SUCCESS;
- }
- $authHost = sprintf(
- '--host=%s --port=%s --user=%s %s %s',
- escapeshellarg($host),
- escapeshellarg($port),
- escapeshellarg($user),
- $password !== '' ? '--password=' . escapeshellarg($password) : '',
- $sslOptions,
- );
- if ($this->option('drop')) {
- $this->info("Пересоздаю БД {$database}...");
- $sql = sprintf(
- 'DROP DATABASE IF EXISTS `%s`; CREATE DATABASE `%s` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;',
- str_replace('`', '', $database),
- str_replace('`', '', $database),
- );
- $dropCmd = "mysql {$authHost} -e " . escapeshellarg($sql);
- $drop = Process::fromShellCommandline($dropCmd);
- $drop->setTimeout(null);
- $drop->run();
- if (!$drop->isSuccessful()) {
- $this->error('Ошибка пересоздания БД: ' . $drop->getErrorOutput());
- return self::FAILURE;
- }
- }
- $isGz = str_ends_with($file, '.gz');
- $reader = $isGz ? 'gunzip -c ' : 'cat ';
- $cmd = $reader . escapeshellarg($file) . ' | ' . escapeshellarg($mysqlBinary) . " {$authHost} --default-character-set=utf8mb4 " . escapeshellarg($database);
- $this->info("Импортирую {$file} → {$database}");
- $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()) {
- $this->error('Ошибка импорта: ' . $process->getErrorOutput());
- return self::FAILURE;
- }
- $this->info('Импорт завершён.');
- return self::SUCCESS;
- }
- private function resolveImportFile(string $file): string
- {
- if ($file === '') {
- return $file;
- }
- if (is_file($file)) {
- return $file;
- }
- return storage_path('db/' . ltrim($file, '/'));
- }
- /**
- * @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;
- }
- }
|