From a30ad13f55c02d54b8e7bdf2e36768f3183c1ade Mon Sep 17 00:00:00 2001 From: Joey Smith Date: Sat, 25 Jul 2026 02:59:35 -0500 Subject: [PATCH 1/3] Initial implementation. Testing are passing but not sure how happy I am with it. Signed-off-by: Joey Smith --- src/Connection.php | 317 ++++++++++++++++------------- src/MysqliConnectionParameters.php | 70 +++++++ src/MysqliSslOptions.php | 38 ++++ 3 files changed, 286 insertions(+), 139 deletions(-) create mode 100644 src/MysqliConnectionParameters.php create mode 100644 src/MysqliSslOptions.php diff --git a/src/Connection.php b/src/Connection.php index 1cd009f..ed7e40e 100644 --- a/src/Connection.php +++ b/src/Connection.php @@ -28,6 +28,8 @@ class Connection extends AbstractConnection implements DriverAwareInterface { protected Driver $driver; + protected ?MysqliConnectionParameters $params = null; + /** @var mysqli */ protected $resource; @@ -37,48 +39,42 @@ class Connection extends AbstractConnection implements DriverAwareInterface * @throws InvalidArgumentException */ public function __construct( - array|mysqli|null $connectionInfo = null + array|mysqli $connectionInfo, ) { if (is_array($connectionInfo)) { $this->setConnectionParameters($connectionInfo); - } elseif ($connectionInfo instanceof mysqli) { + } + + if ($connectionInfo instanceof mysqli) { $this->setResource($connectionInfo); - } elseif (null !== $connectionInfo) { - throw new Exception\InvalidArgumentException( - '$connection must be an array of parameters, a mysqli object or null' - ); } } - public function setDriver(DriverInterface $driver): DriverAwareInterface + /** @inheritDoc */ + #[Override] + public function beginTransaction(): ConnectionInterface { - $this->driver = $driver; + if (! $this->isConnected()) { + $this->connect(); + } + + $this->resource->autocommit(false); + $this->inTransaction = true; return $this; } /** @inheritDoc */ #[Override] - public function getCurrentSchema(): string|false + public function commit(): ConnectionInterface { if (! $this->isConnected()) { $this->connect(); } - $result = $this->resource->query('SELECT DATABASE()'); - $r = $result->fetch_row(); - - return $r[0]; - } - - /** - * Set resource - * - * @return $this Provides a fluent interface - */ - public function setResource(mysqli $resource): static - { - $this->resource = $resource; + $this->resource->commit(); + $this->inTransaction = false; + $this->resource->autocommit(true); return $this; } @@ -91,104 +87,25 @@ public function connect(): ConnectionInterface return $this; } - /** @var array $p */ - $p = $this->connectionParameters; - - // given a list of key names, test for existence in $p - /** @var string[] $names */ - $findParameterValue = function (array $names) use ($p): string|null { - foreach ($names as $name) { - if (isset($p[$name])) { - return $p[$name]; - } - } - - return null; - }; - - /** @var string|null $hostname */ - $hostname = $findParameterValue(['hostname', 'host']); - $username = $findParameterValue(['username', 'user']); - $password = $findParameterValue(['password', 'passwd', 'pw']); - $database = $findParameterValue(['database', 'dbname', 'db', 'schema']); - /** @var int|null $port */ - $port = isset($p['port']) ? (int) $p['port'] : null; - /** @var string|null $socket */ - $socket = $p['socket'] ?? null; - - // phpcs:ignore WebimpressCodingStandard.NamingConventions.ValidVariableName.NotCamelCaps - $useSSL = $p['use_ssl'] ?? 0; - $clientKey = $p['client_key'] ?? ''; - $clientCert = $p['client_cert'] ?? ''; - $caCert = $p['ca_cert'] ?? ''; - $caPath = $p['ca_path'] ?? ''; - $cipher = $p['cipher'] ?? ''; + $params = $this->params ??= MysqliConnectionParameters::fromArray($this->connectionParameters); $this->resource = $this->createResource(); - if (! empty($p['driver_options'])) { - foreach ($p['driver_options'] as $option => $value) { - if (is_string($option)) { - $option = strtoupper($option); - if (! defined($option)) { - continue; - } - $option = constant($option); - } - $this->resource->options($option, $value); - } - } - - $flags = null; - - // phpcs:ignore WebimpressCodingStandard.NamingConventions.ValidVariableName.NotCamelCaps - if ($useSSL && ! $socket) { - // Even though mysqli docs are not quite clear on this, MYSQLI_CLIENT_SSL - // needs to be set to make sure SSL is used. ssl_set can also cause it to - // be implicitly set, but only when any of the parameters is non-empty. - $flags = MYSQLI_CLIENT_SSL; - $this->resource->ssl_set($clientKey, $clientCert, $caCert, $caPath, $cipher); - //MYSQLI_CLIENT_SSL_DONT_VERIFY_SERVER_CERT is not valid option, needs to be set as flag - if ( - isset($p['driver_options'][MYSQLI_CLIENT_SSL_DONT_VERIFY_SERVER_CERT]) - ) { - $flags |= MYSQLI_CLIENT_SSL_DONT_VERIFY_SERVER_CERT; - } + if ([] !== $params->driverOptions) { + $this->applyDriverOptions($this->resource, $params->driverOptions); } - try { - $flags === null - ? $this->resource->real_connect($hostname, $username, $password, $database, $port, $socket) - : $this->resource->real_connect($hostname, $username, $password, $database, $port, $socket, $flags); - } catch (GenericException) { - throw new Exception\RuntimeException( - 'Connection error', - $this->resource->connect_errno, - new Exception\ErrorException($this->resource->connect_error, $this->resource->connect_errno) - ); - } + $flags = $this->applySsl($this->resource, $params); - if ($this->resource->connect_error) { - throw new Exception\RuntimeException( - 'Connection error', - $this->resource->connect_errno, - new Exception\ErrorException($this->resource->connect_error, $this->resource->connect_errno) - ); - } + $this->performRealConnect($this->resource, $params, $flags); - if (! empty($p['charset'])) { - $this->resource->set_charset($p['charset']); + if (! empty($params->charset)) { + $this->resource->set_charset($params->charset); } return $this; } - /** @inheritDoc */ - public function isConnected(): bool - { - return $this->resource instanceof mysqli; - } - /** @inheritDoc */ #[Override] public function disconnect(): ConnectionInterface @@ -200,33 +117,57 @@ public function disconnect(): ConnectionInterface return $this; } - /** @inheritDoc */ + /** + * {@inheritDoc} + * + * @throws Exception\InvalidQueryException + */ #[Override] - public function beginTransaction(): ConnectionInterface + public function execute(string $sql): ?ResultInterface { if (! $this->isConnected()) { $this->connect(); } - $this->resource->autocommit(false); - $this->inTransaction = true; + $this->profiler?->profilerStart($sql); - return $this; + $resultResource = $this->resource->query($sql); + + $this->profiler?->profilerFinish(); + + // if the returnValue is something other than a mysqli_result, bypass wrapping it + if (false === $resultResource) { + throw new Exception\InvalidQueryException($this->resource->error); + } + + return $this->driver->createResult($resultResource === true ? $this->resource : $resultResource); } /** @inheritDoc */ #[Override] - public function commit(): ConnectionInterface + public function getCurrentSchema(): string|false { if (! $this->isConnected()) { $this->connect(); } - $this->resource->commit(); - $this->inTransaction = false; - $this->resource->autocommit(true); + $result = $this->resource->query('SELECT DATABASE()'); + $r = $result->fetch_row(); - return $this; + return $r[0]; + } + + /** @inheritDoc */ + #[Override] + public function getLastGeneratedValue(?string $name = null): string|int|false|null + { + return $this->resource->insert_id; + } + + /** @inheritDoc */ + public function isConnected(): bool + { + return $this->resource instanceof mysqli; } /** @inheritDoc */ @@ -249,36 +190,40 @@ public function rollback(): ConnectionInterface } /** - * {@inheritDoc} + * Set connection parameters * - * @throws Exception\InvalidQueryException + * Also normalizes the raw array into a {@see MysqliConnectionParameters} value + * object, cached for the lifetime of the connection so that {@see connect()} + * does not need to re-parse the raw array on every call. + * + * @return $this Provides a fluent interface */ #[Override] - public function execute($sql): ?ResultInterface + public function setConnectionParameters(array $connectionParameters): ConnectionInterface { - if (! $this->isConnected()) { - $this->connect(); - } + parent::setConnectionParameters($connectionParameters); + $this->params = MysqliConnectionParameters::fromArray($connectionParameters); - $this->profiler?->profilerStart($sql); - - $resultResource = $this->resource->query($sql); - - $this->profiler?->profilerFinish($sql); + return $this; + } - // if the returnValue is something other than a mysqli_result, bypass wrapping it - if ($resultResource === false) { - throw new Exception\InvalidQueryException($this->resource->error); - } + public function setDriver(DriverInterface $driver): DriverAwareInterface + { + $this->driver = $driver; - return $this->driver->createResult($resultResource === true ? $this->resource : $resultResource); + return $this; } - /** @inheritDoc */ - #[Override] - public function getLastGeneratedValue(?string $name = null): string|int|false|null + /** + * Set resource + * + * @return $this Provides a fluent interface + */ + public function setResource(mysqli $resource): static { - return $this->resource->insert_id; + $this->resource = $resource; + + return $this; } /** @@ -292,4 +237,98 @@ protected function createResource() { return new mysqli(); } + + /** + * Apply mysqli `driver_options` to the resource prior to connecting. + * + * String keys are resolved against defined `MYSQLI_*` constants (case-insensitively); + * unresolvable string keys are ignored. Integer keys (e.g. raw `MYSQLI_*` constants) + * are passed through unchanged. + * + * @param array $driverOptions + */ + private function applyDriverOptions(mysqli $resource, array $driverOptions): void + { + foreach ($driverOptions as $option => $value) { + if (is_string($option)) { + $option = strtoupper($option); + if (! defined($option)) { + continue; + } + $option = constant($option); + } + $resource->options($option, $value); + } + } + + /** + * Configure SSL on the resource, if requested, and return the connect flags to use. + * + * Even though mysqli docs are not quite clear on this, MYSQLI_CLIENT_SSL needs to be + * set to make sure SSL is used. ssl_set can also cause it to be implicitly set, but + * only when any of the parameters is non-empty. + */ + private function applySsl(mysqli $resource, MysqliConnectionParameters $params): ?int + { + if (! $params->ssl->enabled || $params->socket) { + return null; + } + + $flags = MYSQLI_CLIENT_SSL; + $resource->ssl_set( + $params->ssl->key, + $params->ssl->cert, + $params->ssl->caCert, + $params->ssl->caPath, + $params->ssl->cipher, + ); + + // MYSQLI_CLIENT_SSL_DONT_VERIFY_SERVER_CERT is not valid option, needs to be set as flag + if (isset($params->driverOptions[MYSQLI_CLIENT_SSL_DONT_VERIFY_SERVER_CERT])) { + $flags |= MYSQLI_CLIENT_SSL_DONT_VERIFY_SERVER_CERT; + } + + return $flags; + } + + /** + * @throws Exception\RuntimeException + */ + private function performRealConnect(mysqli $resource, MysqliConnectionParameters $params, ?int $flags): void + { + try { + null === $flags + ? $resource->real_connect( + $params->hostname, + $params->username, + $params->password, + $params->database, + $params->port, + $params->socket, + ) + : $resource->real_connect( + $params->hostname, + $params->username, + $params->password, + $params->database, + $params->port, + $params->socket, + $flags, + ); + } catch (GenericException) { + throw new Exception\RuntimeException( + 'Connection error', + $resource->connect_errno, + new Exception\ErrorException($resource->connect_error, $resource->connect_errno), + ); + } + + if ($resource->connect_error) { + throw new Exception\RuntimeException( + 'Connection error', + $resource->connect_errno, + new Exception\ErrorException($resource->connect_error, $resource->connect_errno), + ); + } + } } diff --git a/src/MysqliConnectionParameters.php b/src/MysqliConnectionParameters.php new file mode 100644 index 0000000..81d9a59 --- /dev/null +++ b/src/MysqliConnectionParameters.php @@ -0,0 +1,70 @@ + $driverOptions + */ + public function __construct( + public ?string $hostname = null, + public ?string $username = null, + public ?string $password = null, + public ?string $database = null, + public ?int $port = null, + public ?string $socket = null, + public ?string $charset = null, + public array $driverOptions = [], + public MysqliSslOptions $ssl = new MysqliSslOptions(), + ) {} + + /** + * @param array $params + */ + public static function fromArray(array $params): self + { + $hostname = $username = $password = $database = $port = $socket = $charset = null; + $driverOptions = []; + + foreach ($params as $key => $value) { + $result = match (strtolower((string) $key)) { + 'hostname', 'host' => $hostname = (string) $value, + 'username', 'user' => $username = (string) $value, + 'password', 'passwd', 'pw' => $password = (string) $value, + 'database', 'dbname', 'db', 'schema' => $database = (string) $value, + 'port' => $port = (int) $value, + 'socket' => $socket = (string) $value, + 'charset' => $charset = (string) $value, + 'driver_options' => $driverOptions = is_array($value) ? $value : [], + default => null, + }; + } + unset($result); + + return new self( + hostname: $hostname, + username: $username, + password: $password, + database: $database, + port: $port, + socket: $socket, + charset: $charset, + driverOptions: $driverOptions, + ssl: MysqliSslOptions::fromArray($params), + ); + } +} diff --git a/src/MysqliSslOptions.php b/src/MysqliSslOptions.php new file mode 100644 index 0000000..1078906 --- /dev/null +++ b/src/MysqliSslOptions.php @@ -0,0 +1,38 @@ + $params Raw connection parameters, as accepted by + * {@see \PhpDb\Mysql\Connection::setConnectionParameters()}. + */ + public static function fromArray(array $params): self + { + return new self( + enabled: (bool) ($params['use_ssl'] ?? false), + key: (string) ($params['client_key'] ?? ''), + cert: (string) ($params['client_cert'] ?? ''), + caCert: (string) ($params['ca_cert'] ?? ''), + caPath: (string) ($params['ca_path'] ?? ''), + cipher: (string) ($params['cipher'] ?? ''), + ); + } +} From f55420f65e725fb9d3b1c95d3cf9e7915f90956a Mon Sep 17 00:00:00 2001 From: Joey Smith Date: Sat, 25 Jul 2026 22:06:28 -0500 Subject: [PATCH 2/3] stashing changes before branching for mago changes Signed-off-by: Joey Smith --- src/Connection.php | 5 ++--- src/MysqliConnectionParameters.php | 26 ++++++++++++++++++-------- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/src/Connection.php b/src/Connection.php index ed7e40e..5061e99 100644 --- a/src/Connection.php +++ b/src/Connection.php @@ -195,8 +195,6 @@ public function rollback(): ConnectionInterface * Also normalizes the raw array into a {@see MysqliConnectionParameters} value * object, cached for the lifetime of the connection so that {@see connect()} * does not need to re-parse the raw array on every call. - * - * @return $this Provides a fluent interface */ #[Override] public function setConnectionParameters(array $connectionParameters): ConnectionInterface @@ -288,7 +286,8 @@ private function applySsl(mysqli $resource, MysqliConnectionParameters $params): $flags |= MYSQLI_CLIENT_SSL_DONT_VERIFY_SERVER_CERT; } - return $flags; + if ($params->hasDriverOptions()) + return $flags; } /** diff --git a/src/MysqliConnectionParameters.php b/src/MysqliConnectionParameters.php index 81d9a59..d891718 100644 --- a/src/MysqliConnectionParameters.php +++ b/src/MysqliConnectionParameters.php @@ -56,15 +56,25 @@ public static function fromArray(array $params): self unset($result); return new self( - hostname: $hostname, - username: $username, - password: $password, - database: $database, - port: $port, - socket: $socket, - charset: $charset, + hostname : $hostname, + username : $username, + password : $password, + database : $database, + port : $port, + socket : $socket, + charset : $charset, driverOptions: $driverOptions, - ssl: MysqliSslOptions::fromArray($params), + ssl : MysqliSslOptions::fromArray($params), ); } + + public function hasDriverOption(string $key): bool + { + return $this->driverOptions[$key] ?? null !== null; + } + + public function hasDriverOptions(): bool + { + return [] !== $this->driverOptions; + } } From 2b1d39a2837ba3aa730ce39e817711d6da30bde0 Mon Sep 17 00:00:00 2001 From: Joey Smith Date: Sat, 25 Jul 2026 22:09:51 -0500 Subject: [PATCH 3/3] remove unneeded methods Signed-off-by: Joey Smith --- src/Connection.php | 3 +-- src/MysqliConnectionParameters.php | 10 ---------- 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/src/Connection.php b/src/Connection.php index 5061e99..3153ee5 100644 --- a/src/Connection.php +++ b/src/Connection.php @@ -286,8 +286,7 @@ private function applySsl(mysqli $resource, MysqliConnectionParameters $params): $flags |= MYSQLI_CLIENT_SSL_DONT_VERIFY_SERVER_CERT; } - if ($params->hasDriverOptions()) - return $flags; + return $flags; } /** diff --git a/src/MysqliConnectionParameters.php b/src/MysqliConnectionParameters.php index d891718..81d1a10 100644 --- a/src/MysqliConnectionParameters.php +++ b/src/MysqliConnectionParameters.php @@ -67,14 +67,4 @@ public static function fromArray(array $params): self ssl : MysqliSslOptions::fromArray($params), ); } - - public function hasDriverOption(string $key): bool - { - return $this->driverOptions[$key] ?? null !== null; - } - - public function hasDriverOptions(): bool - { - return [] !== $this->driverOptions; - } }