Introduce MySQLi Connection initializers

parent c9ce80ac
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Driver\Mysqli\Exception;
use Doctrine\DBAL\Driver\Mysqli\MysqliException;
use mysqli;
use function sprintf;
/**
* @internal
*
* @psalm-immutable
*/
final class InvalidCharset extends MysqliException
{
public static function fromCharset(mysqli $connection, string $charset): self
{
return new self(
sprintf('Failed to set charset "%s": %s', $charset, $connection->error),
$connection->sqlstate,
$connection->errno
);
}
}
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Driver\Mysqli\Exception;
use Doctrine\DBAL\Driver\Mysqli\MysqliException;
use function sprintf;
/**
* @internal
*
* @psalm-immutable
*/
final class InvalidOption extends MysqliException
{
/**
* @param mixed $value
*/
public static function fromOption(int $option, $value): self
{
return new self(
sprintf('Failed to set option %d with value "%s"', $option, $value)
);
}
}
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Driver\Mysqli;
use mysqli;
interface Initializer
{
/**
* @throws MysqliException
*/
public function initialize(mysqli $connection): void;
}
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Driver\Mysqli\Initializer;
use Doctrine\DBAL\Driver\Mysqli\Exception\InvalidCharset;
use Doctrine\DBAL\Driver\Mysqli\Initializer;
use mysqli;
final class Charset implements Initializer
{
/** @var string */
private $charset;
public function __construct(string $charset)
{
$this->charset = $charset;
}
public function initialize(mysqli $connection): void
{
if ($connection->set_charset($this->charset)) {
return;
}
throw InvalidCharset::fromCharset($connection, $this->charset);
}
}
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Driver\Mysqli\Initializer;
use Doctrine\DBAL\Driver\Mysqli\Exception\InvalidOption;
use Doctrine\DBAL\Driver\Mysqli\Initializer;
use mysqli;
use function mysqli_options;
final class Options implements Initializer
{
/** @var array<int,mixed> */
private $options;
/**
* @param array<int,mixed> $options
*/
public function __construct(array $options)
{
$this->options = $options;
}
public function initialize(mysqli $connection): void
{
foreach ($this->options as $option => $value) {
if (! mysqli_options($connection, $option, $value)) {
throw InvalidOption::fromOption($option, $value);
}
}
}
}
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Driver\Mysqli\Initializer;
use Doctrine\DBAL\Driver\Mysqli\Initializer;
use mysqli;
final class Secure implements Initializer
{
/** @var string|null */
private $key;
/** @var string|null */
private $cert;
/** @var string|null */
private $ca;
/** @var string|null */
private $capath;
/** @var string|null */
private $cipher;
public function __construct(?string $key, ?string $cert, ?string $ca, ?string $capath, ?string $cipher)
{
$this->key = $key;
$this->cert = $cert;
$this->ca = $ca;
$this->capath = $capath;
$this->cipher = $cipher;
}
public function initialize(mysqli $connection): void
{
$connection->ssl_set($this->key, $this->cert, $this->ca, $this->capath, $this->cipher);
}
}
......@@ -2,6 +2,9 @@
namespace Doctrine\DBAL\Driver\Mysqli;
use Doctrine\DBAL\Driver\Mysqli\Initializer\Charset;
use Doctrine\DBAL\Driver\Mysqli\Initializer\Options;
use Doctrine\DBAL\Driver\Mysqli\Initializer\Secure;
use Doctrine\DBAL\Driver\PingableConnection;
use Doctrine\DBAL\Driver\Result as ResultInterface;
use Doctrine\DBAL\Driver\ServerInfoAwareConnection;
......@@ -9,24 +12,12 @@ use Doctrine\DBAL\Driver\Statement as DriverStatement;
use Doctrine\DBAL\ParameterType;
use mysqli;
use function defined;
use function count;
use function floor;
use function in_array;
use function ini_get;
use function mysqli_errno;
use function mysqli_error;
use function mysqli_init;
use function mysqli_options;
use function sprintf;
use function stripos;
use const MYSQLI_INIT_COMMAND;
use const MYSQLI_OPT_CONNECT_TIMEOUT;
use const MYSQLI_OPT_LOCAL_INFILE;
use const MYSQLI_READ_DEFAULT_FILE;
use const MYSQLI_READ_DEFAULT_GROUP;
use const MYSQLI_SERVER_PUBLIC_KEY;
class MysqliConnection implements PingableConnection, ServerInfoAwareConnection
{
/**
......@@ -62,11 +53,19 @@ class MysqliConnection implements PingableConnection, ServerInfoAwareConnection
}
$flags = $driverOptions[static::OPTION_FLAGS] ?? null;
unset($driverOptions[static::OPTION_FLAGS]);
$this->conn = mysqli_init();
$this->setSecureConnection($params);
$this->setDriverOptions($driverOptions);
$preInitializers = $postInitializers = [];
$preInitializers = $this->withOptions($preInitializers, $driverOptions);
$preInitializers = $this->withSecure($preInitializers, $params);
$postInitializers = $this->withCharset($postInitializers, $params);
foreach ($preInitializers as $initializer) {
$initializer->initialize($this->conn);
}
if (! @$this->conn->real_connect($host, $username, $password, $dbname, $port, $socket, $flags)) {
throw new MysqliException(
......@@ -76,11 +75,9 @@ class MysqliConnection implements PingableConnection, ServerInfoAwareConnection
);
}
if (! isset($params['charset'])) {
return;
foreach ($postInitializers as $initializer) {
$initializer->initialize($this->conn);
}
$this->conn->set_charset($params['charset']);
}
/**
......@@ -187,90 +184,69 @@ class MysqliConnection implements PingableConnection, ServerInfoAwareConnection
}
/**
* Apply the driver options to the connection.
* Pings the server and re-connects when `mysqli.reconnect = 1`
*
* @param mixed[] $driverOptions
* @return bool
*/
public function ping()
{
return $this->conn->ping();
}
/**
* @param list<Initializer> $initializers
* @param array<int,mixed> $options
*
* @throws MysqliException When one of of the options is not supported.
* @throws MysqliException When applying doesn't work - e.g. due to incorrect value.
* @return list<Initializer>
*/
private function setDriverOptions(array $driverOptions = []): void
private function withOptions(array $initializers, array $options): array
{
$supportedDriverOptions = [
MYSQLI_OPT_CONNECT_TIMEOUT,
MYSQLI_OPT_LOCAL_INFILE,
MYSQLI_INIT_COMMAND,
MYSQLI_READ_DEFAULT_FILE,
MYSQLI_READ_DEFAULT_GROUP,
];
if (defined('MYSQLI_SERVER_PUBLIC_KEY')) {
$supportedDriverOptions[] = MYSQLI_SERVER_PUBLIC_KEY;
if (count($options) !== 0) {
$initializers[] = new Options($options);
}
$exceptionMsg = "%s option '%s' with value '%s'";
foreach ($driverOptions as $option => $value) {
if ($option === static::OPTION_FLAGS) {
continue;
}
if (! in_array($option, $supportedDriverOptions, true)) {
throw new MysqliException(
sprintf($exceptionMsg, 'Unsupported', $option, $value)
);
}
if (@mysqli_options($this->conn, $option, $value)) {
continue;
}
$msg = sprintf($exceptionMsg, 'Failed to set', $option, $value);
$msg .= sprintf(', error: %s (%d)', mysqli_error($this->conn), mysqli_errno($this->conn));
throw new MysqliException(
$msg,
$this->conn->sqlstate,
$this->conn->errno
);
}
return $initializers;
}
/**
* Pings the server and re-connects when `mysqli.reconnect = 1`
* @param list<Initializer> $initializers
* @param array<string,mixed> $params
*
* @return bool
* @return list<Initializer>
*/
public function ping()
private function withSecure(array $initializers, array $params): array
{
return $this->conn->ping();
if (
isset($params['ssl_key']) ||
isset($params['ssl_cert']) ||
isset($params['ssl_ca']) ||
isset($params['ssl_capath']) ||
isset($params['ssl_cipher'])
) {
$initializers[] = new Secure(
$params['ssl_key'] ?? null,
$params['ssl_cert'] ?? null,
$params['ssl_ca'] ?? null,
$params['ssl_capath'] ?? null,
$params['ssl_cipher'] ?? null
);
}
return $initializers;
}
/**
* Establish a secure connection
* @param list<Initializer> $initializers
* @param array<string,mixed> $params
*
* @param mixed[] $params
*
* @throws MysqliException
* @return list<Initializer>
*/
private function setSecureConnection(array $params): void
private function withCharset(array $initializers, array $params): array
{
if (
! isset($params['ssl_key']) &&
! isset($params['ssl_cert']) &&
! isset($params['ssl_ca']) &&
! isset($params['ssl_capath']) &&
! isset($params['ssl_cipher'])
) {
return;
if (isset($params['charset'])) {
$initializers[] = new Charset($params['charset']);
}
$this->conn->ssl_set(
$params['ssl_key'] ?? null,
$params['ssl_cert'] ?? null,
$params['ssl_ca'] ?? null,
$params['ssl_capath'] ?? null,
$params['ssl_cipher'] ?? null
);
return $initializers;
}
}
......@@ -44,7 +44,7 @@ class ConnectionTest extends FunctionalTestCase
{
$this->expectException(MysqliException::class);
$this->getConnection(['hello' => 'world']); // use local infile
$this->getConnection([12345 => 'world']);
}
public function testPing(): void
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment