protected function restoreEnv(string $extractDir, string $appDir, OutputInterface $output, InteractiveConsole $interactions): array
{
- $oldEnv = EnvironmentLoader::load($extractDir);
+ $oldEnv = EnvironmentLoader::loadMergedWithCurrentEnv($extractDir);
$currentEnv = EnvironmentLoader::load($appDir);
$envContents = file_get_contents(Paths::join($extractDir, '.env'));
$appEnvPath = Paths::real(Paths::join($appDir, '.env'));
protected function restoreDatabase(string $appDir, string $extractDir): void
{
$dbDump = Paths::join($extractDir, 'db.sql');
- $currentEnv = EnvironmentLoader::load($appDir);
+ $currentEnv = EnvironmentLoader::loadMergedWithCurrentEnv($appDir);
$mysql = MySqlRunner::fromEnvOptions($currentEnv);
// Drop existing tables
namespace Cli\Services;
use Exception;
+use Symfony\Component\Process\Exception\ProcessTimedOutException;
class MySqlRunner
{
}
}
+ protected function createOptionsFile(): string
+ {
+ $path = tempnam(sys_get_temp_dir(), 'bs-cli-mysql-opts');
+ $contents = "[client]\nuser={$this->user}\nhost={$this->host}\nport={$this->port}\npassword={$this->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
{
- $output = (new ProgramRunner('mysql', '/usr/bin/mysql'))
- ->withEnvironment(['MYSQL_PWD' => $this->password])
- ->withTimeout(300)
- ->withIdleTimeout(300)
- ->runCapturingStdErr([
- '-h', $this->host,
- '-P', $this->port,
- '-u', $this->user,
- '--protocol=TCP',
- $this->database,
- '-e', "show tables;"
- ]);
-
- return !$output;
+ $optionsFile = $this->createOptionsFile();
+
+ try {
+ $stdErr = $this->getProgramRunnerInstance()
+ ->withTimeout(300)
+ ->withIdleTimeout(300)
+ ->runCapturingStdErr([
+ "--defaults-file={$optionsFile}",
+ $this->database,
+ '-e', "show tables;"
+ ]);
+ unlink($optionsFile);
+ } catch (Exception $exception) {
+ unlink($optionsFile);
+ throw $exception;
+ }
+
+ return !$stdErr;
}
public function importSqlFile(string $sqlFilePath): void
{
- $output = (new ProgramRunner('mysql', '/usr/bin/mysql'))
- ->withEnvironment(['MYSQL_PWD' => $this->password])
- ->withTimeout(300)
- ->withIdleTimeout(300)
- ->runCapturingStdErr([
- '-h', $this->host,
- '-P', $this->port,
- '-u', $this->user,
- '--protocol=TCP',
- $this->database,
- '-e', "source {$sqlFilePath}"
- ]);
+ $optionsFile = $this->createOptionsFile();
+
+ try {
+ $output = $this->getProgramRunnerInstance()
+ ->withTimeout(300)
+ ->withIdleTimeout(300)
+ ->runCapturingStdErr([
+ "--defaults-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);
+ throw new Exception("Failed mysql file import with errors:\n{$output}");
}
}
$errors = "";
$warnings = "";
$hasOutput = false;
+ $optionsFile = $this->createOptionsFile();
try {
- (new ProgramRunner('mysqldump', '/usr/bin/mysqldump'))
+ (new ProgramRunner(['mariadb-dump', 'mysqldump'], '/usr/bin/mysqldump'))
->withTimeout(300)
->withIdleTimeout(300)
- ->withEnvironment(['MYSQL_PWD' => $this->password])
+ ->withAdditionalPathLocation('C:\xampp\mysql\bin')
->runWithoutOutputCallbacks([
- '-h', $this->host,
- '-P', $this->port,
- '-u', $this->user,
- '--protocol=TCP',
+ "--defaults-file={$optionsFile}",
'--single-transaction',
'--no-tablespaces',
$this->database,
foreach ($lines as $line) {
if (str_starts_with(strtolower($line), 'warning: ')) {
$warnings .= $line;
- } else {
+ } 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.");
return new static($host, $username, $password, $database, $port);
}
-}
\ No newline at end of file
+}
namespace Cli\Services;
+use Exception;
use Symfony\Component\Process\ExecutableFinder;
use Symfony\Component\Process\Process;
class ProgramRunner
{
+ /**
+ * Names of the program to search for.
+ * @var string[]
+ */
+ protected array $programNames = [];
+
protected int $timeout = 240;
protected int $idleTimeout = 15;
protected array $environment = [];
protected array $additionalProgramDirectories = [];
public function __construct(
- protected string $program,
+ string|array $program,
protected string $defaultPath
) {
+ $this->programNames = is_string($program) ? [$program] : $program;
}
public function withTimeout(int $timeoutSeconds): static
}
/**
- * @throws \Exception
+ * @throws Exception
*/
public function ensureFound(): void
{
try {
$this->ensureFound();
return true;
- } catch (\Exception $exception) {
+ } catch (Exception $exception) {
return false;
}
}
return $process;
}
+ /**
+ * @throws Exception
+ */
protected function resolveProgramPath(): string
{
- $executableFinder = new ExecutableFinder();
- $path = $executableFinder->find($this->program, $this->defaultPath, $this->additionalProgramDirectories);
+ foreach ($this->programNames as $programName) {
+ $executableFinder = new ExecutableFinder();
+ $path = $executableFinder->find($programName, $this->defaultPath, $this->additionalProgramDirectories);
- if (is_null($path) || !is_file($path)) {
- throw new \Exception("Could not locate \"{$this->program}\" program.");
+ if (!is_null($path) && is_file($path)) {
+ return $path;
+ }
}
- return $path;
+ $combinedProgram = implode('|', $this->programNames);
+ throw new Exception("Could not locate \"{$combinedProgram}\" program.");
}
}