Skip to content
13 changes: 13 additions & 0 deletions packages/database-mysql/src/Connection/MySqlConnection.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,19 @@ public function connect(): void
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
];

if ($this->config->sslRootCert !== null) {
$options[Pdo\Mysql::ATTR_SSL_CA] = $this->config->sslRootCert;
$options[Pdo\Mysql::ATTR_SSL_VERIFY_SERVER_CERT] = $this->config->sslVerifyServerCert;
}

if ($this->config->sslCert !== null) {
$options[Pdo\Mysql::ATTR_SSL_CERT] = $this->config->sslCert;
}

if ($this->config->sslKey !== null) {
$options[Pdo\Mysql::ATTR_SSL_KEY] = $this->config->sslKey;
}

try {
$this->pdo = $this->createPdo(
$this->getDsn(),
Expand Down
251 changes: 243 additions & 8 deletions packages/database-mysql/tests/Connection/MySqlConnectionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,19 +21,42 @@ function createTestDatabaseConfig(
string $database = 'test',
string $username = 'root',
string $password = '',
?string $sslCa = null,
bool $sslVerifyServerCert = false,
?string $sslCert = null,
?string $sslKey = null,
): DatabaseConfig {
$tempDir = sys_get_temp_dir() . '/marko_mysql_test_' . uniqid();
mkdir($tempDir . '/config', recursive: true);

$configArray = [
'driver' => 'mysql',
'host' => $host,
'port' => $port,
'database' => $database,
'username' => $username,
'password' => $password,
];

if ($sslCa !== null) {
$configArray['ssl_ca'] = $sslCa;
}

if ($sslVerifyServerCert) {
$configArray['ssl_verify_server_cert'] = true;
}

if ($sslCert !== null) {
$configArray['ssl_cert'] = $sslCert;
}

if ($sslKey !== null) {
$configArray['ssl_key'] = $sslKey;
}

file_put_contents(
$tempDir . '/config/database.php',
'<?php return ' . var_export([
'driver' => 'mysql',
'host' => $host,
'port' => $port,
'database' => $database,
'username' => $username,
'password' => $password,
], true) . ';',
'<?php return ' . var_export($configArray, true) . ';',
);

$paths = new ProjectPaths($tempDir);
Expand Down Expand Up @@ -540,6 +563,218 @@ protected function createPdo(
expect($result)->toBe('success');
});

it('passes SSL CA cert in PDO options when configured', function (): void {
$capturedOptions = [];
$config = createTestDatabaseConfig(sslCa: '/path/to/ca.pem');

$connection = new class ($config, $capturedOptions) extends MySqlConnection
{
public function __construct(
DatabaseConfig $config,
private array &$capturedOptions,
) {
parent::__construct($config);
}

protected function createPdo(
string $dsn,
string $username,
string $password,
array $options,
): PDO {
$this->capturedOptions = $options;

return new PDO('sqlite::memory:');
}
};

$connection->connect();

expect($capturedOptions[Pdo\Mysql::ATTR_SSL_CA])->toBe('/path/to/ca.pem');
});

it('sets SSL verify server cert when configured', function (): void {
$capturedOptions = [];
$config = createTestDatabaseConfig(sslCa: '/path/to/ca.pem', sslVerifyServerCert: true);

$connection = new class ($config, $capturedOptions) extends MySqlConnection
{
public function __construct(
DatabaseConfig $config,
private array &$capturedOptions,
) {
parent::__construct($config);
}

protected function createPdo(
string $dsn,
string $username,
string $password,
array $options,
): PDO {
$this->capturedOptions = $options;

return new PDO('sqlite::memory:');
}
};

$connection->connect();

expect($capturedOptions[Pdo\Mysql::ATTR_SSL_CA])->toBe('/path/to/ca.pem')
->and($capturedOptions[Pdo\Mysql::ATTR_SSL_VERIFY_SERVER_CERT])->toBeTrue();
});

it('defaults SSL verify server cert to true when ssl_ca is set', function (): void {
$capturedOptions = [];
$config = createTestDatabaseConfig(sslCa: '/path/to/ca.pem');

$connection = new class ($config, $capturedOptions) extends MySqlConnection
{
public function __construct(
DatabaseConfig $config,
private array &$capturedOptions,
) {
parent::__construct($config);
}

protected function createPdo(
string $dsn,
string $username,
string $password,
array $options,
): PDO {
$this->capturedOptions = $options;

return new PDO('sqlite::memory:');
}
};

$connection->connect();

expect($capturedOptions[Pdo\Mysql::ATTR_SSL_VERIFY_SERVER_CERT])->toBeTrue();
});

it('passes SSL client cert in PDO options when configured', function (): void {
$capturedOptions = [];
$config = createTestDatabaseConfig(sslCert: '/path/to/client-cert.pem', sslKey: '/path/to/client-key.pem');

$connection = new class ($config, $capturedOptions) extends MySqlConnection
{
public function __construct(
DatabaseConfig $config,
private array &$capturedOptions,
) {
parent::__construct($config);
}

protected function createPdo(
string $dsn,
string $username,
string $password,
array $options,
): PDO {
$this->capturedOptions = $options;

return new PDO('sqlite::memory:');
}
};

$connection->connect();

expect($capturedOptions[Pdo\Mysql::ATTR_SSL_CERT])->toBe('/path/to/client-cert.pem');
});

it('passes SSL client key in PDO options when configured', function (): void {
$capturedOptions = [];
$config = createTestDatabaseConfig(sslCert: '/path/to/client-cert.pem', sslKey: '/path/to/client-key.pem');

$connection = new class ($config, $capturedOptions) extends MySqlConnection
{
public function __construct(
DatabaseConfig $config,
private array &$capturedOptions,
) {
parent::__construct($config);
}

protected function createPdo(
string $dsn,
string $username,
string $password,
array $options,
): PDO {
$this->capturedOptions = $options;

return new PDO('sqlite::memory:');
}
};

$connection->connect();

expect($capturedOptions[Pdo\Mysql::ATTR_SSL_KEY])->toBe('/path/to/client-key.pem');
});

it('omits SSL client cert and key from PDO options when not configured', function (): void {
$capturedOptions = [];
$config = createTestDatabaseConfig();

$connection = new class ($config, $capturedOptions) extends MySqlConnection
{
public function __construct(
DatabaseConfig $config,
private array &$capturedOptions,
) {
parent::__construct($config);
}

protected function createPdo(
string $dsn,
string $username,
string $password,
array $options,
): PDO {
$this->capturedOptions = $options;

return new PDO('sqlite::memory:');
}
};

$connection->connect();

expect($capturedOptions)->not->toHaveKey(Pdo\Mysql::ATTR_SSL_CERT)
->and($capturedOptions)->not->toHaveKey(Pdo\Mysql::ATTR_SSL_KEY);
});

it('omits SSL CA cert from PDO options when not configured', function (): void {
$capturedOptions = [];
$config = createTestDatabaseConfig();

$connection = new class ($config, $capturedOptions) extends MySqlConnection
{
public function __construct(
DatabaseConfig $config,
private array &$capturedOptions,
) {
parent::__construct($config);
}

protected function createPdo(
string $dsn,
string $username,
string $password,
array $options,
): PDO {
$this->capturedOptions = $options;

return new PDO('sqlite::memory:');
}
};

$connection->connect();

expect($capturedOptions)->not->toHaveKey(Pdo\Mysql::ATTR_SSL_CA);
});

it('prevents nested transactions (throws exception)', function (): void {
$config = createTestDatabaseConfig();
$connection = new class ($config) extends MySqlConnection
Expand Down
26 changes: 23 additions & 3 deletions packages/database-pgsql/src/Connection/PgSqlConnection.php
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,25 @@ protected function createPdo(

private function buildDsn(): string
{
return "pgsql:host={$this->config->host};port={$this->config->port};dbname={$this->config->database}";
$dsn = "pgsql:host={$this->config->host};port={$this->config->port};dbname={$this->config->database}";

if ($this->config->sslMode !== null) {
$dsn .= ";sslmode={$this->config->sslMode}";
}

if ($this->config->sslRootCert !== null) {
$dsn .= ";sslrootcert={$this->config->sslRootCert}";
}

if ($this->config->sslCert !== null) {
$dsn .= ";sslcert={$this->config->sslCert}";
}

if ($this->config->sslKey !== null) {
$dsn .= ";sslkey={$this->config->sslKey}";
}

return $dsn;
}

/**
Expand Down Expand Up @@ -163,8 +181,10 @@ public function prepare(
/**
* @param array<int|string, mixed> $bindings
*/
private function bindValues(PDOStatement $statement, array $bindings): void
{
private function bindValues(
PDOStatement $statement,
array $bindings,
): void {
foreach ($bindings as $key => $value) {
$param = is_int($key) ? $key + 1 : $key;
$type = match (true) {
Expand Down
Loading