$option) { throw new Exception("Could not find a valid value for the \"{$option}\" database option."); } } } protected function createOptionsFile(): string { $path = tempnam(sys_get_temp_dir(), 'bs-cli-mysql-opts'); $password = str_replace('\\', '\\\\', $this->password); $contents = "[client]\nuser='{$this->user}'\nhost='{$this->host}'\nport={$this->port}\npassword='{$password}'\nprotocol=TCP"; file_put_contents($path, $contents); chmod($path, 0600); return $path; } protected function getProgramRunnerInstance(): ProgramRunner { return (new ProgramRunner(['mariadb', 'mysql'], '/usr/bin/mysql')); } public function testConnection(): bool { $optionsFile = $this->createOptionsFile(); try { $stdErr = $this->getProgramRunnerInstance() ->withTimeout(300) ->withIdleTimeout(300) ->runCapturingStdErr([ "--defaults-extra-file={$optionsFile}", $this->database, '-e', "show tables;" ]); unlink($optionsFile); } catch (Exception $exception) { unlink($optionsFile); throw $exception; } return !$stdErr; } public function importSqlFile(string $sqlFilePath): void { $optionsFile = $this->createOptionsFile(); try { $output = $this->getProgramRunnerInstance() ->withTimeout(300) ->withIdleTimeout(300) ->runCapturingStdErr([ "--defaults-extra-file={$optionsFile}", $this->database, '-e', "source {$sqlFilePath}" ]); unlink($optionsFile); } catch (Exception $exception) { unlink($optionsFile); throw $exception; } if ($output) { throw new Exception("Failed mysql file import with errors:\n{$output}"); } } public function dropTablesSql(): string { return <<<'HEREDOC' SET FOREIGN_KEY_CHECKS = 0; SET GROUP_CONCAT_MAX_LEN=32768; SET @tables = NULL; SELECT GROUP_CONCAT('`', table_name, '`') INTO @tables FROM information_schema.tables WHERE table_schema = (SELECT DATABASE()); SELECT IFNULL(@tables,'dummy') INTO @tables; SET @tables = CONCAT('DROP TABLE IF EXISTS ', @tables); PREPARE stmt FROM @tables; EXECUTE stmt; DEALLOCATE PREPARE stmt; SET FOREIGN_KEY_CHECKS = 1; HEREDOC; } public function runDumpToFile(string $filePath): string { $file = fopen($filePath, 'w'); $errors = ""; $warnings = ""; $hasOutput = false; $optionsFile = $this->createOptionsFile(); try { (new ProgramRunner(['mariadb-dump', 'mysqldump'], '/usr/bin/mysqldump')) ->withTimeout(300) ->withIdleTimeout(300) ->withAdditionalPathLocation('C:\xampp\mysql\bin') ->runWithoutOutputCallbacks([ "--defaults-extra-file={$optionsFile}", '--single-transaction', '--no-tablespaces', $this->database, ], function ($data) use (&$file, &$hasOutput) { fwrite($file, $data); $hasOutput = true; }, function ($error) use (&$errors, &$warnings) { $lines = explode("\n", $error); foreach ($lines as $line) { if (str_starts_with(strtolower($line), 'warning: ')) { $warnings .= $line; } else if (!empty(trim($line))) { $errors .= $line . "\n"; } } }); unlink($optionsFile); } catch (\Exception $exception) { fclose($file); unlink($optionsFile); if ($exception instanceof ProcessTimedOutException) { if (!$hasOutput) { throw new Exception("mysqldump operation timed-out.\nNo data has been received so the connection to your database may have failed."); } else { throw new Exception("mysqldump operation timed-out after data was received."); } } throw new Exception($exception->getMessage()); } fclose($file); if ($errors) { throw new Exception("Failed mysqldump with errors:\n" . $errors); } return $warnings; } public static function fromEnvOptions(array $env): static { $host = ($env['DB_HOST'] ?? ''); $username = ($env['DB_USERNAME'] ?? ''); $password = ($env['DB_PASSWORD'] ?? ''); $database = ($env['DB_DATABASE'] ?? ''); $port = intval($env['DB_PORT'] ?? 3306); return new static($host, $username, $password, $database, $port); } }