1 <?php declare(strict_types=1);
3 namespace Cli\Services;
6 use Symfony\Component\Process\Exception\ProcessTimedOutException;
10 public function __construct(
11 protected string $host,
12 protected string $user,
13 protected string $password,
14 protected string $database,
15 protected int $port = 3306
22 public function ensureOptionsSet(): void
24 $options = ['host', 'user', 'password', 'database'];
25 foreach ($options as $option) {
26 if (!$this->$option) {
27 throw new Exception("Could not find a valid value for the \"{$option}\" database option.");
32 protected function createOptionsFile(): string
34 $path = tempnam(sys_get_temp_dir(), 'bs-cli-mysql-opts');
35 $password = str_replace('\\', '\\\\', $this->password);
36 $contents = "[client]\nuser='{$this->user}'\nhost='{$this->host}'\nport={$this->port}\npassword='{$password}'\nprotocol=TCP";
37 file_put_contents($path, $contents);
42 protected function getProgramRunnerInstance(): ProgramRunner
44 return (new ProgramRunner(['mariadb', 'mysql'], '/usr/bin/mysql'));
47 public function testConnection(): bool
49 $optionsFile = $this->createOptionsFile();
52 $stdErr = $this->getProgramRunnerInstance()
54 ->withIdleTimeout(300)
55 ->runCapturingStdErr([
56 "--defaults-extra-file={$optionsFile}",
61 } catch (Exception $exception) {
69 public function importSqlFile(string $sqlFilePath): void
71 $optionsFile = $this->createOptionsFile();
74 $output = $this->getProgramRunnerInstance()
76 ->withIdleTimeout(300)
77 ->runCapturingStdErr([
78 "--defaults-extra-file={$optionsFile}",
80 '-e', "source {$sqlFilePath}"
83 } catch (Exception $exception) {
89 throw new Exception("Failed mysql file import with errors:\n{$output}");
93 public function dropTablesSql(): string
96 SET FOREIGN_KEY_CHECKS = 0;
97 SET GROUP_CONCAT_MAX_LEN=32768;
99 SELECT GROUP_CONCAT('`', table_name, '`') INTO @tables
100 FROM information_schema.tables
101 WHERE table_schema = (SELECT DATABASE());
102 SELECT IFNULL(@tables,'dummy') INTO @tables;
104 SET @tables = CONCAT('DROP TABLE IF EXISTS ', @tables);
105 PREPARE stmt FROM @tables;
107 DEALLOCATE PREPARE stmt;
108 SET FOREIGN_KEY_CHECKS = 1;
112 public function runDumpToFile(string $filePath): string
114 $file = fopen($filePath, 'w');
118 $optionsFile = $this->createOptionsFile();
121 (new ProgramRunner(['mariadb-dump', 'mysqldump'], '/usr/bin/mysqldump'))
123 ->withIdleTimeout(300)
124 ->withAdditionalPathLocation('C:\xampp\mysql\bin')
125 ->runWithoutOutputCallbacks([
126 "--defaults-extra-file={$optionsFile}",
127 '--single-transaction',
130 ], function ($data) use (&$file, &$hasOutput) {
131 fwrite($file, $data);
133 }, function ($error) use (&$errors, &$warnings) {
134 $lines = explode("\n", $error);
135 foreach ($lines as $line) {
136 if (str_starts_with(strtolower($line), 'warning: ')) {
138 } else if (!empty(trim($line))) {
139 $errors .= $line . "\n";
143 unlink($optionsFile);
144 } catch (\Exception $exception) {
146 unlink($optionsFile);
147 if ($exception instanceof ProcessTimedOutException) {
149 throw new Exception("mysqldump operation timed-out.\nNo data has been received so the connection to your database may have failed.");
151 throw new Exception("mysqldump operation timed-out after data was received.");
154 throw new Exception($exception->getMessage());
160 throw new Exception("Failed mysqldump with errors:\n" . $errors);
166 public static function fromEnvOptions(array $env): static
168 $host = ($env['DB_HOST'] ?? '');
169 $username = ($env['DB_USERNAME'] ?? '');
170 $password = ($env['DB_PASSWORD'] ?? '');
171 $database = ($env['DB_DATABASE'] ?? '');
172 $port = intval($env['DB_PORT'] ?? 3306);
174 return new static($host, $username, $password, $database, $port);